From d2c1cc78e526c8a7c1d71ffba5f1f7460d4f937e Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Fri, 2 Jul 2010 14:57:58 +0000 Subject: [PATCH] Add cm:geographic Aspect, which has cm:latitude and cm:longitude, and update the Tika auto parser to map to this (plus tests) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20925 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/model/contentModel.xml | 14 ++++++++ .../metadata/PdfBoxMetadataExtracter.java | 3 +- .../metadata/TikaAutoMetadataExtracter.java | 2 ++ .../TikaAutoMetadataExtracter.properties | 5 +++ .../TikaAutoMetadataExtracterTest.java | 28 +++++++++++++-- .../TikaPoweredMetadataExtracter.java | 32 +++++++++++++++++- source/test-resources/quick/quickGEO.jpg | Bin 0 -> 16510 bytes 7 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 source/test-resources/quick/quickGEO.jpg diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index ac565c07ef..25b036161a 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -1175,6 +1175,20 @@ + + Geographic + + + Latitude + d:double + + + Longitude + d:double + + + + diff --git a/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java index 0ee0db9c03..61c9951e25 100644 --- a/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java @@ -33,12 +33,11 @@ import org.apache.tika.parser.pdf.PDFParser; * title: -- cm:title * subject: -- cm:description * created: -- cm:created + * (custom metadata): -- * * * Uses Apache Tika * - * TODO - Update Tika to handle custom metadata - * * @author Jesper Steen Møller * @author Derek Hulley */ diff --git a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.java index 021889b730..7dd75d8c54 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.java @@ -40,6 +40,8 @@ import org.apache.tika.parser.Parser; * subject: -- cm:description * created: -- cm:created * comments: + *

geo:lat: -- cm:latitude + *

geo:long: -- cm:longitude * * * @author Nick Burch diff --git a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.properties b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.properties index b0d67029d8..de6520459b 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.properties +++ b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracter.properties @@ -16,3 +16,8 @@ author=cm:author title=cm:title description=cm:description created=cm:created + +geo\:lat=cm:latitude +geo\:long=cm:longitude +#tiff\:ImageWidth=cm:imageWidth +#tiff\:ImageLength=cm:imageHeight \ No newline at end of file diff --git a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java index bf8ba991e9..8858b9b4a2 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java @@ -210,16 +210,38 @@ public class TikaAutoMetadataExtracterTest extends AbstractMetadataExtracterTest assertEquals("409", p.get("width")); assertEquals("92", p.get("height")); assertEquals("8 8 8", p.get("Data BitsPerSample")); + + + // Geo tagged image + p = openAndCheck("GEO.jpg", "image/jpeg"); + assertEquals("100 pixels", p.get("Image Width")); + assertEquals("68 pixels", p.get("Image Height")); + assertEquals("8 bits", p.get("Data Precision")); + assertEquals(QUICK_TITLE, p.get("Comments")); + assertEquals("12.54321", p.get("geo:lat")); + assertEquals("-54.1234", p.get("geo:long")); + + // Map and check + Map properties = new HashMap(); + ContentReader reader = new FileContentReader(open("GEO.jpg")); + reader.setMimetype("image/jpeg"); + extracter.extract(reader, properties); + assertEquals(12.54321, properties.get(QName.createQName("http://www.alfresco.org/model/content/1.0","latitude"))); + assertEquals(-54.1234, properties.get(QName.createQName("http://www.alfresco.org/model/content/1.0","longitude"))); } - private Map openAndCheck(String fileBase, String expMimeType) throws Throwable { + private File open(String fileBase) throws Throwable { String filename = "quick" + fileBase; URL url = AbstractContentTransformerTest.class.getClassLoader().getResource("quick/" + filename); File file = new File(url.getFile()); - + assertTrue(file.exists()); + return file; + } + private Map openAndCheck(String fileBase, String expMimeType) throws Throwable { // Cheat and ask Tika for the mime type! + File file = open(fileBase); AutoDetectParser ap = new AutoDetectParser(); Metadata metadata = new Metadata(); - metadata.set(Metadata.RESOURCE_NAME_KEY, filename); + metadata.set(Metadata.RESOURCE_NAME_KEY, "quick"+fileBase); MediaType mt = ap.getDetector().detect( new BufferedInputStream(new FileInputStream(file)), metadata); String mimetype = mt.toString(); diff --git a/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java index d514fec080..87da227b5f 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.content.metadata; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -31,9 +32,11 @@ import java.util.HashSet; import java.util.Locale; import java.util.Map; +import org.alfresco.repo.content.filestore.FileContentReader; import org.alfresco.service.cmr.repository.ContentReader; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; @@ -109,6 +112,7 @@ public abstract class TikaPoweredMetadataExtracter extends AbstractMappingMetada { super(supportedMimeTypes); + // TODO Once TIKA-451 is fixed this list will get nicer this.tikaDateFormats = new DateFormat[] { new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US), @@ -116,6 +120,10 @@ public abstract class TikaPoweredMetadataExtracter extends AbstractMappingMetada new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US), new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("yyyy-MM-dd", Locale.US), + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"), + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US), + new SimpleDateFormat("yyyy/MM/dd"), + new SimpleDateFormat("yyyy/MM/dd", Locale.US), new SimpleDateFormat("EEE MMM dd hh:mm:ss zzz yyyy"), new SimpleDateFormat("EEE MMM dd hh:mm:ss zzz yyyy", Locale.US) }; @@ -169,6 +177,28 @@ public abstract class TikaPoweredMetadataExtracter extends AbstractMappingMetada return properties; } + /** + * There seems to be some sort of issue with some downstream + * 3rd party libraries, and input streams that come from + * a {@link ContentReader}. This happens most often with + * JPEG and Tiff files. + * For these cases, buffer out to a local file if not + * already there + */ + private InputStream getInputStream(ContentReader reader) throws IOException { + if("image/jpeg".equals(reader.getMimetype()) || + "image/tiff".equals(reader.getMimetype())) { + if(reader instanceof FileContentReader) { + return TikaInputStream.get( ((FileContentReader)reader).getFile() ); + } else { + File tmpFile = File.createTempFile("tika", "tmp"); + reader.getContent(tmpFile); + return TikaInputStream.get(tmpFile); + } + } + return reader.getContentInputStream(); + } + @Override protected Map extractRaw(ContentReader reader) throws Throwable { @@ -177,7 +207,7 @@ public abstract class TikaPoweredMetadataExtracter extends AbstractMappingMetada InputStream is = null; try { - is = reader.getContentInputStream(); + is = getInputStream(reader); Parser parser = getParser(); Metadata metadata = new Metadata(); ParseContext context = new ParseContext(); diff --git a/source/test-resources/quick/quickGEO.jpg b/source/test-resources/quick/quickGEO.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a5609b3bd696fbbd68ad446f21295c69627e2a37 GIT binary patch literal 16510 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 literal 0 HcmV?d00001