Browse Source

Limit length of iptc values according to the spec

pull/1574/head
Brian Popow 6 years ago
parent
commit
6a54eef87f
  1. 77
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  2. 119
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
  3. 121
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
  4. 37
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  5. 6
      src/ImageSharp/Metadata/Profiles/IPTC/README.md
  6. 56
      tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs

77
src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs

@ -37,6 +37,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.Initialize(); this.Initialize();
} }
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class
/// by making a copy from another IPTC profile.
/// </summary>
/// <param name="other">The other IPTC profile, from which the clone should be made from.</param>
private IptcProfile(IptcProfile other) private IptcProfile(IptcProfile other)
{ {
Guard.NotNull(other, nameof(other)); Guard.NotNull(other, nameof(other));
@ -85,16 +90,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// <returns>The values found with the specified tag.</returns> /// <returns>The values found with the specified tag.</returns>
public List<IptcValue> GetValues(IptcTag tag) public List<IptcValue> GetValues(IptcTag tag)
{ {
var values = new List<IptcValue>(); var iptcValues = new List<IptcValue>();
foreach (IptcValue iptcValue in this.Values) foreach (IptcValue iptcValue in this.Values)
{ {
if (iptcValue.Tag == tag) if (iptcValue.Tag == tag)
{ {
values.Add(iptcValue); iptcValues.Add(iptcValue);
} }
} }
return values; return iptcValues;
} }
/// <summary> /// <summary>
@ -157,21 +162,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
} }
/// <summary> /// <summary>
/// Sets the value of the specified tag. /// Sets the value for the specified tag.
/// </summary> /// </summary>
/// <param name="tag">The tag of the iptc value.</param> /// <param name="tag">The tag of the iptc value.</param>
/// <param name="encoding">The encoding to use when storing the bytes.</param> /// <param name="encoding">The encoding to use when storing the bytes.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public void SetValue(IptcTag tag, Encoding encoding, string value) /// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true)
{ {
Guard.NotNull(encoding, nameof(encoding)); Guard.NotNull(encoding, nameof(encoding));
if (!this.IsRepeatable(tag)) if (!tag.IsRepeatable())
{ {
foreach (IptcValue iptcValue in this.Values) foreach (IptcValue iptcValue in this.Values)
{ {
if (iptcValue.Tag == tag) if (iptcValue.Tag == tag)
{ {
iptcValue.Strict = strict;
iptcValue.Encoding = encoding; iptcValue.Encoding = encoding;
iptcValue.Value = value; iptcValue.Value = value;
return; 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));
} }
/// <summary> /// <summary>
@ -187,7 +197,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// </summary> /// </summary>
/// <param name="tag">The tag of the iptc value.</param> /// <param name="tag">The tag of the iptc value.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public void SetValue(IptcTag tag, string value) => this.SetValue(tag, Encoding.UTF8, value); /// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
/// <summary> /// <summary>
/// Updates the data of the profile. /// Updates the data of the profile.
@ -251,56 +265,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
if ((count > 0) && (i + count <= this.Data.Length)) if ((count > 0) && (i + count <= this.Data.Length))
{ {
Buffer.BlockCopy(this.Data, i, iptcData, 0, count); 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; 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;
}
}
} }
} }

119
src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs

@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{ {
/// <summary> /// <summary>
/// All iptc tags. /// All iptc tags relevant for images.
/// </summary> /// </summary>
public enum IptcTag public enum IptcTag
{ {
@ -14,222 +14,245 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
Unknown = -1, Unknown = -1,
/// <summary> /// <summary>
/// Record version, not repeatable. /// Record version identifying the version of the Information Interchange Model.
/// Not repeatable. Max length is 2.
/// </summary> /// </summary>
RecordVersion = 0, RecordVersion = 0,
/// <summary> /// <summary>
/// Object type, not repeatable. /// Object type, not repeatable. Max Length is 67.
/// </summary> /// </summary>
ObjectType = 3, ObjectType = 3,
/// <summary> /// <summary>
/// Object attribute. /// Object attribute. Max length is 68.
/// </summary> /// </summary>
ObjectAttribute = 4, ObjectAttribute = 4,
/// <summary> /// <summary>
/// Object Name, not repeatable. /// Object Name, not repeatable. Max length is 64.
/// </summary> /// </summary>
Name = 5, Name = 5,
/// <summary> /// <summary>
/// Edit status, not repeatable. /// Edit status, not repeatable. Max length is 64.
/// </summary> /// </summary>
EditStatus = 7, EditStatus = 7,
/// <summary> /// <summary>
/// Editorial update, not repeatable. /// Editorial update, not repeatable. Max length is 2.
/// </summary> /// </summary>
EditorialUpdate = 8, EditorialUpdate = 8,
/// <summary> /// <summary>
/// Urgency, not repeatable. /// Urgency, not repeatable. Max length is 2.
/// </summary> /// </summary>
Urgency = 10, Urgency = 10,
/// <summary> /// <summary>
/// Subject Reference. /// Subject Reference. Max length is 236.
/// </summary> /// </summary>
SubjectReference = 12, SubjectReference = 12,
/// <summary> /// <summary>
/// Category, not repeatable. /// Category, not repeatable. Max length is 3.
/// </summary> /// </summary>
Category = 15, Category = 15,
/// <summary> /// <summary>
/// Supplemental categories. /// Supplemental categories. Max length is 32.
/// </summary> /// </summary>
SupplementalCategories = 20, SupplementalCategories = 20,
/// <summary> /// <summary>
/// Fixture identifier, not repeatable. /// Fixture identifier, not repeatable. Max length is 32.
/// </summary> /// </summary>
FixtureIdentifier = 22, FixtureIdentifier = 22,
/// <summary> /// <summary>
/// Keywords. /// Keywords. Max length is 64.
/// </summary> /// </summary>
Keywords = 25, Keywords = 25,
/// <summary> /// <summary>
/// Location code. /// Location code. Max length is 3.
/// </summary> /// </summary>
LocationCode = 26, LocationCode = 26,
/// <summary> /// <summary>
/// Location name. /// Location name. Max length is 64.
/// </summary> /// </summary>
LocationName = 27, LocationName = 27,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
ReleaseDate = 30, ReleaseDate = 30,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
ReleaseTime = 35, ReleaseTime = 35,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
ExpirationDate = 37, ExpirationDate = 37,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
ExpirationTime = 38, ExpirationTime = 38,
/// <summary> /// <summary>
/// Special instructions, not repeatable. /// Special instructions, not repeatable. Max length is 256.
/// </summary> /// </summary>
SpecialInstructions = 40, SpecialInstructions = 40,
/// <summary> /// <summary>
/// Action advised, not repeatable. /// Action advised, not repeatable. Max length is 2.
/// </summary> /// </summary>
ActionAdvised = 42, ActionAdvised = 42,
/// <summary> /// <summary>
/// Reference service. /// Reference service. Max length is 10.
/// </summary> /// </summary>
ReferenceService = 45, ReferenceService = 45,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
ReferenceDate = 47, ReferenceDate = 47,
/// <summary> /// <summary>
/// ReferenceNumber. /// ReferenceNumber. Max length is 8.
/// </summary> /// </summary>
ReferenceNumber = 50, ReferenceNumber = 50,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
CreatedDate = 55, CreatedDate = 55,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
CreatedTime = 60, CreatedTime = 60,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
DigitalCreationDate = 62, DigitalCreationDate = 62,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
DigitalCreationTime = 63, DigitalCreationTime = 63,
/// <summary> /// <summary>
/// Originating program, not repeatable. /// Originating program, not repeatable. Max length is 32.
/// </summary> /// </summary>
OriginatingProgram = 65, OriginatingProgram = 65,
/// <summary> /// <summary>
/// Program version, not repeatable. /// Program version, not repeatable. Max length is 10.
/// </summary> /// </summary>
ProgramVersion = 70, ProgramVersion = 70,
/// <summary> /// <summary>
/// Object cycle, not repeatable. /// Object cycle, not repeatable. Max length is 1.
/// </summary> /// </summary>
ObjectCycle = 75, ObjectCycle = 75,
/// <summary> /// <summary>
/// Byline. /// Byline. Max length is 32.
/// </summary> /// </summary>
Byline = 80, Byline = 80,
/// <summary> /// <summary>
/// Byline title. /// Byline title. Max length is 32.
/// </summary> /// </summary>
BylineTitle = 85, BylineTitle = 85,
/// <summary> /// <summary>
/// City, not repeatable. /// City, not repeatable. Max length is 32.
/// </summary> /// </summary>
City = 90, City = 90,
/// <summary> /// <summary>
/// Sub location, not repeatable. /// Sub location, not repeatable. Max length is 32.
/// </summary> /// </summary>
SubLocation = 92, SubLocation = 92,
/// <summary> /// <summary>
/// Province/State, not repeatable. /// Province/State, not repeatable. Max length is 32.
/// </summary> /// </summary>
ProvinceState = 95, ProvinceState = 95,
/// <summary> /// <summary>
/// Country code, not repeatable. /// Country code, not repeatable. Max length is 3.
/// </summary> /// </summary>
CountryCode = 100, CountryCode = 100,
/// <summary> /// <summary>
/// Country, not repeatable. /// Country, not repeatable. Max length is 64.
/// </summary> /// </summary>
Country = 101, Country = 101,
/// <summary> /// <summary>
/// Original transmission reference, not repeatable. /// Original transmission reference, not repeatable. Max length is 32.
/// </summary> /// </summary>
OriginalTransmissionReference = 103, OriginalTransmissionReference = 103,
/// <summary> /// <summary>
/// Headline, not repeatable. /// Headline, not repeatable. Max length is 256.
/// </summary> /// </summary>
Headline = 105, Headline = 105,
/// <summary> /// <summary>
/// Credit, not repeatable. /// Credit, not repeatable. Max length is 32.
/// </summary> /// </summary>
Credit = 110, Credit = 110,
/// <summary> /// <summary>
/// Source, not repeatable. /// Source, not repeatable. Max length is 32.
/// </summary> /// </summary>
Source = 115, Source = 115,
/// <summary> /// <summary>
/// Copyright notice, not repeatable. /// Copyright notice, not repeatable. Max length is 128.
/// </summary> /// </summary>
CopyrightNotice = 116, CopyrightNotice = 116,
/// <summary> /// <summary>
/// Contact. /// Contact. Max length 128.
/// </summary> /// </summary>
Contact = 118, Contact = 118,
/// <summary> /// <summary>
/// Caption, not repeatable. /// Caption, not repeatable. Max length is 2000.
/// </summary> /// </summary>
Caption = 120, Caption = 120,
@ -239,17 +262,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
LocalCaption = 121, LocalCaption = 121,
/// <summary> /// <summary>
/// Caption writer. /// Caption writer. Max length is 32.
/// </summary> /// </summary>
CaptionWriter = 122, CaptionWriter = 122,
/// <summary> /// <summary>
/// Image type, not repeatable. /// Image type, not repeatable. Max length is 2.
/// </summary> /// </summary>
ImageType = 130, ImageType = 130,
/// <summary> /// <summary>
/// Image orientation, not repeatable. /// Image orientation, not repeatable. Max length is 1.
/// </summary> /// </summary>
ImageOrientation = 131, ImageOrientation = 131,

121
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
{
/// <summary>
/// Extension methods for IPTC tags.
/// </summary>
public static class IptcTagExtensions
{
/// <summary>
/// Maximum length of the IPTC value with the given tag according to the specification.
/// </summary>
/// <param name="tag">The tag to check the max length for.</param>
/// <returns>The maximum length.</returns>
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
};
}
/// <summary>
/// Determines if the given tag can be repeated according to the specification.
/// </summary>
/// <param name="tag">The tag to check.</param>
/// <returns>True, if the tag can occur multiple times.</returns>
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;
}
}
}
}

37
src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs

@ -28,24 +28,35 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
} }
this.Tag = other.Tag; 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)); Guard.NotNull(value, nameof(value));
this.Strict = strict;
this.Tag = tag; this.Tag = tag;
this.data = value; this.data = value;
this.encoding = Encoding.UTF8; 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.Tag = tag;
this.encoding = encoding; this.encoding = encoding;
this.Value = value; this.Value = value;
} }
internal IptcValue(IptcTag tag, string value, bool strict)
{
this.Strict = strict;
this.Tag = tag;
this.encoding = Encoding.UTF8;
this.Value = value;
}
/// <summary> /// <summary>
/// Gets or sets the encoding to use for the Value. /// Gets or sets the encoding to use for the Value.
/// </summary> /// </summary>
@ -66,6 +77,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// </summary> /// </summary>
public IptcTag Tag { get; } public IptcTag Tag { get; }
/// <summary>
/// Gets or sets a value indicating whether to be enforce value length restrictions according
/// to the specification.
/// </summary>
public bool Strict { get; set; }
/// <summary> /// <summary>
/// Gets or sets the value. /// Gets or sets the value.
/// </summary> /// </summary>
@ -76,11 +93,23 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
{ {
this.data = new byte[0]; this.data = Array.Empty<byte>();
} }
else 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;
} }
} }
} }

6
src/ImageSharp/Metadata/Profiles/IPTC/README.md

@ -1,9 +1,11 @@
IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET) 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) - [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/) - [iptc.org](https://www.iptc.org/std/photometadata/documentation/userguide/)
- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf) - [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf)
- [Tag Overview](https://exiftool.org/TagNames/IPTC.html)

56
tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -15,6 +16,31 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
{ {
private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false };
public static IEnumerable<object[]> allIptcTags()
{
foreach (object tag in Enum.GetValues(typeof(IptcTag)))
{
yield return new object[] { tag };
}
}
[Theory]
[MemberData("allIptcTags")]
public void IptcProfile_SetValue_WithStrictOption_Works(IptcTag tag)
{
// arrange
var profile = new IptcProfile();
var value = new string('s', tag.MaxLength() + 1);
var expectedLength = tag.MaxLength();
// act
profile.SetValue(tag, value);
// assert
IptcValue actual = profile.GetValues(tag).First();
Assert.Equal(expectedLength, actual.Value.Length);
}
[Theory] [Theory]
[WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)]
public void ReadIptcMetadata_Works<TPixel>(TestImageProvider<TPixel> provider) public void ReadIptcMetadata_Works<TPixel>(TestImageProvider<TPixel> provider)
@ -91,16 +117,17 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
// assert // assert
Assert.Equal(2, clone.Values.Count()); Assert.Equal(2, clone.Values.Count());
ContainsIptcValue(clone.Values, IptcTag.CaptionWriter, captionWriter); var cloneValues = clone.Values.ToList();
ContainsIptcValue(clone.Values, IptcTag.Caption, "changed"); ContainsIptcValue(cloneValues, IptcTag.CaptionWriter, captionWriter);
ContainsIptcValue(profile.Values, IptcTag.Caption, caption); ContainsIptcValue(cloneValues, IptcTag.Caption, "changed");
ContainsIptcValue(profile.Values.ToList(), IptcTag.Caption, caption);
} }
[Fact] [Fact]
public void IptcValue_CloneIsDeep() public void IptcValue_CloneIsDeep()
{ {
// arrange // arrange
var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test"); var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test", true);
// act // act
IptcValue clone = iptcValue.DeepClone(); IptcValue clone = iptcValue.DeepClone();
@ -132,6 +159,13 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption);
} }
[Fact]
public void IptcProfile_SetNewValue_RespectsMaxLength()
{
// arrange
var profile = new IptcProfile();
}
[Theory] [Theory]
[InlineData(IptcTag.ObjectAttribute)] [InlineData(IptcTag.ObjectAttribute)]
[InlineData(IptcTag.SubjectReference)] [InlineData(IptcTag.SubjectReference)]
@ -153,10 +187,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
var profile = new IptcProfile(); var profile = new IptcProfile();
var expectedValue1 = "test"; var expectedValue1 = "test";
var expectedValue2 = "another one"; var expectedValue2 = "another one";
profile.SetValue(tag, expectedValue1); profile.SetValue(tag, expectedValue1, false);
// act // act
profile.SetValue(tag, expectedValue2); profile.SetValue(tag, expectedValue2, false);
// assert // assert
var values = profile.Values.ToList(); var values = profile.Values.ToList();
@ -204,10 +238,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
// arrange // arrange
var profile = new IptcProfile(); var profile = new IptcProfile();
var expectedValue = "another one"; var expectedValue = "another one";
profile.SetValue(tag, "test"); profile.SetValue(tag, "test", false);
// act // act
profile.SetValue(tag, expectedValue); profile.SetValue(tag, expectedValue, false);
// assert // assert
var values = profile.Values.ToList(); var values = profile.Values.ToList();
@ -244,7 +278,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
// assert // assert
Assert.True(result, "removed result should be true"); Assert.True(result, "removed result should be true");
ContainsIptcValue(profile.Values, IptcTag.Byline, "test"); ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test");
} }
[Fact] [Fact]
@ -264,10 +298,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
Assert.Equal(2, result.Count); Assert.Equal(2, result.Count);
} }
private static void ContainsIptcValue(IEnumerable<IptcValue> values, IptcTag tag, string value) private static void ContainsIptcValue(List<IptcValue> values, IptcTag tag, string value)
{ {
Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}"); Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}");
Assert.True(values.Contains(new IptcValue(tag, System.Text.Encoding.UTF8.GetBytes(value))), $"expected iptc value '{value}' was not found for tag '{tag}'"); Assert.True(values.Contains(new IptcValue(tag, System.Text.Encoding.UTF8.GetBytes(value), false)), $"expected iptc value '{value}' was not found for tag '{tag}'");
} }
private static Image<Rgba32> WriteAndReadJpeg(Image<Rgba32> image) private static Image<Rgba32> WriteAndReadJpeg(Image<Rgba32> image)

Loading…
Cancel
Save