diff --git a/common-lib/src/main/scala/com/gu/mediaservice/lib/elasticsearch/MappingTest.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/elasticsearch/MappingTest.scala index 2b67cde617..011a024120 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/lib/elasticsearch/MappingTest.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/elasticsearch/MappingTest.scala @@ -83,6 +83,7 @@ object MappingTest { iptc = Map("iptc1" -> "value1"), exif = Map("exif1" -> "value1"), exifSub = Map("exifSub1" -> "value1"), + exifInterop = Map("exifInterop1" -> "value1"), xmp = Map( "xmp1" -> JsString("value1"), "xmp2" -> JsNumber(12345), diff --git a/common-lib/src/main/scala/com/gu/mediaservice/model/FileMetadata.scala b/common-lib/src/main/scala/com/gu/mediaservice/model/FileMetadata.scala index 7994723db5..09758a8e87 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/model/FileMetadata.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/model/FileMetadata.scala @@ -11,7 +11,8 @@ case class FileMetadata( iptc: Map[String, String] = Map(), exif: Map[String, String] = Map(), exifSub: Map[String, String] = Map(), - xmp: Map[String, JsValue] = Map(), + exifInterop: Map[String, String] = Map(), + xmp: Map[String, JsValue] = Map(), icc: Map[String, String] = Map(), getty: Map[String, String] = Map(), colourModel: Option[String] = None, @@ -22,6 +23,7 @@ case class FileMetadata( "iptcFieldCount" -> iptc.size, "exifFieldCount" -> exif.size, "exifSubFieldCount" -> exifSub.size, + "exifInteropFieldCount" -> exifInterop.size, "xmpFieldCount" -> xmp.size, "iccFieldCount" -> icc.size, "gettyFieldCount" -> getty.size, @@ -52,6 +54,7 @@ object FileMetadata { (__ \ "iptc").read[Map[String,String]] ~ (__ \ "exif").read[Map[String,String]] ~ (__ \ "exifSub").read[Map[String,String]] ~ + (__ \ "exifInterop").readNullable[Map[String,String]].map(_ getOrElse Map()) ~ (__ \ "xmp").read[Map[String,JsValue]] ~ (__ \ "icc").readNullable[Map[String,String]].map(_ getOrElse Map()).map(removeLongValues) ~ (__ \ "getty").readNullable[Map[String,String]].map(_ getOrElse Map()) ~ @@ -74,6 +77,7 @@ object FileMetadata { (JsPath \ "iptc").write[Map[String,String]] and (JsPath \ "exif").write[Map[String,String]] and (JsPath \ "exifSub").write[Map[String,String]] and + (JsPath \ "exifInterop").write[Map[String,String]] and (JsPath \ "xmp").write[Map[String,JsValue]] and (JsPath \ "icc").write[Map[String,String]].contramap[Map[String, String]](removeLongValues) and (JsPath \ "getty").write[Map[String,String]] and diff --git a/common-lib/src/test/scala/com/gu/mediaservice/lib/metadata/ImageMetadataConverterTest.scala b/common-lib/src/test/scala/com/gu/mediaservice/lib/metadata/ImageMetadataConverterTest.scala index 33cd314c07..f040b4e525 100644 --- a/common-lib/src/test/scala/com/gu/mediaservice/lib/metadata/ImageMetadataConverterTest.scala +++ b/common-lib/src/test/scala/com/gu/mediaservice/lib/metadata/ImageMetadataConverterTest.scala @@ -378,14 +378,14 @@ class ImageMetadataConverterTest extends FunSpec with Matchers { // People in Image it("should populate peopleInImage field of ImageMetadata from corresponding xmp iptc ext fields") { - val fileMetadata = FileMetadata(Map(), Map(), Map(), Map("Iptc4xmpExt:PersonInImage" -> JsArray(Seq(JsString("person 1"))))) + val fileMetadata = FileMetadata(Map(), Map(), Map(), Map(), Map("Iptc4xmpExt:PersonInImage" -> JsArray(Seq(JsString("person 1"))))) val imageMetadata = ImageMetadataConverter.fromFileMetadata(fileMetadata) imageMetadata.peopleInImage should be (Set("person 1")) } it("should populate peopleInImage field of ImageMetadata from multiple corresponding people xmp fields") { val fileMetadata = FileMetadata( - Map(), Map(), Map(), + Map(), Map(), Map(), Map(), Map("Iptc4xmpExt:PersonInImage" -> JsArray(Seq( JsString("person 1"), @@ -400,7 +400,7 @@ class ImageMetadataConverterTest extends FunSpec with Matchers { } it("should distinctly populate peopleInImage field of ImageMetadata from multiple corresponding xmp iptc ext fields") { - val fileMetadata = FileMetadata(Map(), Map(), Map(), + val fileMetadata = FileMetadata(Map(), Map(), Map(), Map(), Map("Iptc4xmpExt:PersonInImage" -> JsArray(Seq( JsString("person 1"), @@ -413,7 +413,7 @@ class ImageMetadataConverterTest extends FunSpec with Matchers { } it("should distinctly populate peopleInImage field of ImageMetadata from multiple corresponding xmp people fields") { - val fileMetadata = FileMetadata(Map(), Map(), Map(), + val fileMetadata = FileMetadata(Map(), Map(), Map(), Map(), Map( "Iptc4xmpExt:PersonInImage" -> JsArray(Seq( JsString("person 1"), diff --git a/common-lib/src/test/scala/com/gu/mediaservice/model/FileMetadataTest.scala b/common-lib/src/test/scala/com/gu/mediaservice/model/FileMetadataTest.scala index 2c4b5444d7..bb67dc7c05 100644 --- a/common-lib/src/test/scala/com/gu/mediaservice/model/FileMetadataTest.scala +++ b/common-lib/src/test/scala/com/gu/mediaservice/model/FileMetadataTest.scala @@ -8,46 +8,46 @@ class FileMetadataTest extends FreeSpec with Matchers with Checkers with Propert "Dehydrate a non-empty object" - { "Leave all short values alone" in { - val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(("hello" -> "goodbye")), Map(), None, Map()) + val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map(("hello" -> "goodbye")), Map(), None, Map()) val json = Json.toJson(fm).toString() - json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"xmp\":{},\"icc\":{\"hello\":\"goodbye\"},\"getty\":{},\"colourModelInformation\":{}}") + json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"exifInterop\":{},\"xmp\":{},\"icc\":{\"hello\":\"goodbye\"},\"getty\":{},\"colourModelInformation\":{}}") } "Remove a single long value" in { val A5000 = (1 to 5000).toList.mkString(",") - val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000), Map(), None, Map()) + val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000), Map(), None, Map()) val json = Json.toJson(fm).toString() - json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"xmp\":{},\"icc\":{\"hello\":\"goodbye\",\"removedFields\":\"A5000\"},\"getty\":{},\"colourModelInformation\":{}}") + json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"exifInterop\":{},\"xmp\":{},\"icc\":{\"hello\":\"goodbye\",\"removedFields\":\"A5000\"},\"getty\":{},\"colourModelInformation\":{}}") } "Remove multiple long values" in { val A5000 = (1 to 5000).toList.mkString(",") val B5000 = (1 to 10000).toList.mkString(",") - val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000, "B5000" -> B5000), Map(), None, Map()) + val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000, "B5000" -> B5000), Map(), None, Map()) val json = Json.toJson(fm).toString() - json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"xmp\":{},\"icc\":{\"hello\":\"goodbye\",\"removedFields\":\"A5000, B5000\"},\"getty\":{},\"colourModelInformation\":{}}") + json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"exifInterop\":{},\"xmp\":{},\"icc\":{\"hello\":\"goodbye\",\"removedFields\":\"A5000, B5000\"},\"getty\":{},\"colourModelInformation\":{}}") } } "Dehydrate and rehydrate a non-empty object" - { "Leave all short values alone" in { - val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(("hello" -> "goodbye")), Map(), None, Map()) + val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map(("hello" -> "goodbye")), Map(), None, Map()) val json = Json.toJson(fm).toString() val fmRehydrated = Json.fromJson[FileMetadata](Json.parse(json)).get - fmRehydrated should be (new FileMetadata(Map(), Map(), Map(), Map(), Map("hello" -> "goodbye"), Map(), None, Map())) + fmRehydrated should be (new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map("hello" -> "goodbye"), Map(), None, Map())) } "Remove a single long value" in { val A5000 = (1 to 5000).toList.mkString(",") - val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000), Map(), None, Map()) + val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000), Map(), None, Map()) val json = Json.toJson(fm).toString() val fmRehydrated = Json.fromJson[FileMetadata](Json.parse(json)).get - fmRehydrated should be ( new FileMetadata(Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "removedFields" -> "A5000"), Map(), None, Map())) + fmRehydrated should be ( new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "removedFields" -> "A5000"), Map(), None, Map())) } "Remove multiple long values" in { val A5000 = (1 to 5000).toList.mkString(",") val B5000 = (1 to 10000).toList.mkString(",") - val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000, "B5000" -> B5000), Map(), None, Map()) + val fm = new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "A5000" -> A5000, "B5000" -> B5000), Map(), None, Map()) val json = Json.toJson(fm).toString() val fmRehydrated = Json.fromJson[FileMetadata](Json.parse(json)).get - fmRehydrated should be ( new FileMetadata(Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "removedFields" -> "A5000, B5000"), Map(), None, Map())) + fmRehydrated should be ( new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map("hello" -> "goodbye", "removedFields" -> "A5000, B5000"), Map(), None, Map())) } } @@ -55,13 +55,13 @@ class FileMetadataTest extends FreeSpec with Matchers with Checkers with Propert "Dehydrate" in { val fm = new FileMetadata() val json = Json.toJson(fm).toString() - json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"xmp\":{},\"icc\":{},\"getty\":{},\"colourModelInformation\":{}}") + json should be ("{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"exifInterop\":{},\"xmp\":{},\"icc\":{},\"getty\":{},\"colourModelInformation\":{}}") } "Rehydrate" in { - val json = "{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"xmp\":{},\"icc\":{},\"getty\":{},\"colourModelInformation\":{}}" + val json = "{\"iptc\":{},\"exif\":{},\"exifSub\":{},\"exifInterop\":{},\"xmp\":{},\"icc\":{},\"getty\":{},\"colourModelInformation\":{}}" val fm = Json.fromJson[FileMetadata](Json.parse(json)).get - fm should be (new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map(), None, Map())) + fm should be (new FileMetadata(Map(), Map(), Map(), Map(), Map(), Map(), Map(), None, Map())) } } diff --git a/image-loader/app/lib/imaging/FileMetadataReader.scala b/image-loader/app/lib/imaging/FileMetadataReader.scala index cdaf0de730..ef2824b4cd 100644 --- a/image-loader/app/lib/imaging/FileMetadataReader.scala +++ b/image-loader/app/lib/imaging/FileMetadataReader.scala @@ -5,7 +5,7 @@ import java.util.concurrent.Executors import com.adobe.internal.xmp.XMPMetaFactory import com.drew.imaging.ImageMetadataReader -import com.drew.metadata.exif.{ExifDirectoryBase, ExifIFD0Directory, ExifSubIFDDirectory} +import com.drew.metadata.exif.{ExifDirectoryBase, ExifIFD0Directory, ExifSubIFDDirectory, ExifInteropDirectory} import com.drew.metadata.icc.IccDirectory import com.drew.metadata.iptc.IptcDirectory import com.drew.metadata.jpeg.JpegDirectory @@ -73,6 +73,7 @@ object FileMetadataReader { iptc = exportDirectory(metadata, classOf[IptcDirectory]), exif = exportDirectory(metadata, classOf[ExifIFD0Directory]), exifSub = exportDirectory(metadata, classOf[ExifSubIFDDirectory]), + exifInterop = exportDirectory(metadata, classOf[ExifInteropDirectory]), xmp = exportXmpPropertiesInTransformedSchema(metadata, imageId), icc = exportDirectory(metadata, classOf[IccDirectory]), getty = exportGettyDirectory(metadata, imageId),