From 8eb474b06e1463ca172128b59b713782eb4bece8 Mon Sep 17 00:00:00 2001 From: rgauss Date: Sat, 19 Mar 2016 16:37:37 -0400 Subject: [PATCH 1/6] Initial commit of tika-exiftool as is --- tika-exiftool/README.md | 16 + tika-exiftool/pom.xml | 183 +++++++ .../exiftool/ExiftoolExternalEmbedder.java | 99 ++++ .../AbstractExiftoolMetadataExtractor.java | 321 +++++++++++ .../tika/parser/exiftool/ExecutableUtils.java | 80 +++ .../parser/exiftool/ExifToolMetadata.java | 510 ++++++++++++++++++ .../parser/exiftool/ExiftoolImageParser.java | 149 +++++ .../ExiftoolIptcMetadataExtractor.java | 146 +++++ .../exiftool/ExiftoolTikaIptcMapper.java | 490 +++++++++++++++++ .../parser/exiftool/ExiftoolTikaMapper.java | 55 ++ .../parser/exiftool/tika.exiftool.properties | 1 + .../ExiftoolExternalEmbedderTest.java | 102 ++++ .../exiftool/ExifToolImageParserTest.java | 365 +++++++++++++ .../src/test/resources/log4j.properties | 24 + .../resources/test-documents/testJPEG.jpg | Bin 0 -> 7686 bytes .../test-documents/testJPEG_IPTC_EXT.jpg | Bin 0 -> 27421 bytes .../resources/test-documents/testPNG_IPTC.png | Bin 0 -> 18511 bytes .../test-documents/testTIFF_IPTC.tif | Bin 0 -> 26164 bytes .../test/resources/test-documents/testTXT.txt | 2 + 19 files changed, 2543 insertions(+) create mode 100644 tika-exiftool/README.md create mode 100644 tika-exiftool/pom.xml create mode 100644 tika-exiftool/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java create mode 100644 tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java create mode 100644 tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java create mode 100644 tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java create mode 100644 tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java create mode 100644 tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java create mode 100644 tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java create mode 100644 tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java create mode 100644 tika-exiftool/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties create mode 100644 tika-exiftool/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java create mode 100644 tika-exiftool/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java create mode 100644 tika-exiftool/src/test/resources/log4j.properties create mode 100644 tika-exiftool/src/test/resources/test-documents/testJPEG.jpg create mode 100644 tika-exiftool/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg create mode 100644 tika-exiftool/src/test/resources/test-documents/testPNG_IPTC.png create mode 100644 tika-exiftool/src/test/resources/test-documents/testTIFF_IPTC.tif create mode 100644 tika-exiftool/src/test/resources/test-documents/testTXT.txt diff --git a/tika-exiftool/README.md b/tika-exiftool/README.md new file mode 100644 index 00000000000..3ec4eeb59f9 --- /dev/null +++ b/tika-exiftool/README.md @@ -0,0 +1,16 @@ +Overview +======== + +tika-exifool is an [Apache Tika](http://tika.apache.org/) external parser +which invokes the command line [ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/) +and can map the output to specific Tika metadata fields. + +An IPTC extractor and mapping to Tika's IPTC metadata is provided. + + +Requirements +============ + +ExifTool must be installed. You can specify the full path to the command line in +`org/apache/tika/parser/exiftool/tika.exiftool.properties` or +`org/apache/tika/parser/exiftool/tika.exiftool.override.properties`. \ No newline at end of file diff --git a/tika-exiftool/pom.xml b/tika-exiftool/pom.xml new file mode 100644 index 00000000000..6ce747adb99 --- /dev/null +++ b/tika-exiftool/pom.xml @@ -0,0 +1,183 @@ + + + + + + 4.0.0 + + org.apache.tika + tika-exiftool + jar + Apache Tika ExifTool + 1.6-20160315 + http://tika.apache.org/ + + + 1.5 + 1.5 + UTF-8 + 1.6 + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + Ray Gauss II + rgauss + Alfresco + http://alfresco.com + -5 + + + + + https://github.com/Alfresco/tika-exiftool + GitHub Issues + + + + https://github.com/Alfresco/tika-exiftool + scm:git:git://github.com/Alfresco/tika-exiftool.git + scm:git:git@github.com:Alfresco/tika-exiftool.git + + + + + apache.snapshots + http://repository.apache.org/snapshots/ + + true + daily + + + + + + + ${project.groupId} + tika-core + ${tika.version} + + + ${project.groupId} + tika-parsers + ${tika.version} + + + commons-logging + commons-logging + 1.1.1 + provided + + + + + junit + junit + 4.10 + test + + + org.slf4j + slf4j-log4j12 + 1.5.6 + test + + + ${project.groupId} + tika-core + ${tika.version} + test-jar + test + + + ${project.groupId} + tika-parsers + ${tika.version} + test-jar + test + + + + + + + maven-compiler-plugin + 2.3.2 + + ${maven.compile.source} + ${maven.compile.target} + + + + + + + org.apache.felix + maven-bundle-plugin + 2.3.4 + + + + + + src/main/resources + + **/tika.exiftool.override.properties + + + + + + src/test/resources + + **/* + + + + src/main/resources + + **/* + + + + + + + + alfresco-thirdparty + https://artifacts.alfresco.com/nexus/content/repositories/thirdparty + + + alfresco-thirdparty + https://artifacts.alfresco.com/nexus/content/repositories/thirdparty + + + + diff --git a/tika-exiftool/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java b/tika-exiftool/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java new file mode 100644 index 00000000000..c59d51e7cf4 --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.embedder.exiftool; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.tika.embedder.ExternalEmbedder; +import org.apache.tika.metadata.Property; +import org.apache.tika.parser.exiftool.ExecutableUtils; +import org.apache.tika.parser.exiftool.ExiftoolTikaMapper; +import org.apache.tika.parser.external.ExternalParser; + +/** + * Convenience class to programmatically create an {@link ExternalEmbedder} which uses ExifTool. + * + * @author rgauss + * @see ExifTool + * + */ +public class ExiftoolExternalEmbedder extends ExternalEmbedder { + + private static final long serialVersionUID = 6037513204935762760L; + + private static final String COMMAND_APPEND_OPERATOR = "+="; + + private final String runtimeExiftoolExecutable; + + private final ExiftoolTikaMapper exiftoolTikaMapper; + + /** + * Default constructor + */ + public ExiftoolExternalEmbedder(ExiftoolTikaMapper exiftoolTikaMapper) { + super(); + this.exiftoolTikaMapper = exiftoolTikaMapper; + this.runtimeExiftoolExecutable = null; + init(); + } + + public ExiftoolExternalEmbedder(ExiftoolTikaMapper exiftoolTikaMapper, String runtimeExiftoolExecutable) { + super(); + this.exiftoolTikaMapper = exiftoolTikaMapper; + this.runtimeExiftoolExecutable = runtimeExiftoolExecutable; + init(); + } + + /** + * Programmatically sets up the metadata to command line arguments map, sets the executable, and append operator. + */ + public void init() { + // Convert the exiftool metadata names into command line arguments + Map metadataCommandArguments = new HashMap(); + for (Property tikaMetadata : exiftoolTikaMapper.getTikaToExiftoolMetadataMap().keySet()) { + List exiftoolMetadataNames = exiftoolTikaMapper.getTikaToExiftoolMetadataMap().get(tikaMetadata); + String[] exiftoolCommandArguments = new String[exiftoolMetadataNames.size()]; + for (int i = 0; i < exiftoolMetadataNames.size(); i++) { + exiftoolCommandArguments[i] = "-" + exiftoolMetadataNames.get(i).getName(); + } + metadataCommandArguments.put(tikaMetadata, exiftoolCommandArguments); + } + setMetadataCommandArguments(metadataCommandArguments); + + setExiftoolExecutable( + ExecutableUtils.getExiftoolExecutable(runtimeExiftoolExecutable)); + setCommandAppendOperator(COMMAND_APPEND_OPERATOR); + } + + /** + * Sets the path to the executable exiftool. + * + * @param exiftoolExecutable + */ + public void setExiftoolExecutable(String exiftoolExecutable) { + String[] cmd = new String[] { exiftoolExecutable, "-v0", + "-o", ExternalParser.OUTPUT_FILE_TOKEN, "-m", + ExternalEmbedder.METADATA_COMMAND_ARGUMENTS_TOKEN, + ExternalParser.INPUT_FILE_TOKEN }; + setCommand(cmd); + } + +} diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java new file mode 100644 index 00000000000..aaf9a9df0b1 --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.namespace.QName; + +import org.apache.tika.exception.TikaException; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.Property; +import org.apache.tika.mime.MediaType; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.parser.Parser; +import org.apache.tika.parser.external.ExternalParser; +import org.apache.tika.parser.xml.ElementMetadataHandler; +import org.apache.tika.parser.xml.XMLParser; +import org.apache.tika.sax.BodyContentHandler; +import org.apache.tika.sax.TeeContentHandler; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * A chained extractor which first calls an ExifTool {@link ExternalParser} requesting XML output + * format then feeds that result into another {@link XMLParser} for the final metadata extraction. + * + * ExifTool command line is required and the path to that executable is defined by exiftool.executable + * in tika.exiftool.properties or tika.exiftool.override.properties. + * + * @author rgauss + * @see ExifTool + * + */ +public abstract class AbstractExiftoolMetadataExtractor { + + private static final Log logger = LogFactory.getLog(AbstractExiftoolMetadataExtractor.class); + + private static final String DEFAULT_CHARSET = "UTF-8"; + private static final MessageFormat FORMAT_EXIFTOOL_XMP_NAMESPACE = new MessageFormat("http://ns.exiftool.ca/XMP/{0}/1.0/"); + + private final Metadata metadata; + private final Set supportedTypes; + private final String runtimeExiftoolExecutable; + private final Collection passthroughXmpProperties; + + public AbstractExiftoolMetadataExtractor(Metadata metadata, Set supportedTypes) { + this.metadata = metadata; + this.supportedTypes = supportedTypes; + this.runtimeExiftoolExecutable = null; + this.passthroughXmpProperties = null; + } + + public AbstractExiftoolMetadataExtractor(Metadata metadata, Set supportedTypes, String runtimeExiftoolExecutable) { + this.metadata = metadata; + this.supportedTypes = supportedTypes; + if (runtimeExiftoolExecutable != null && !runtimeExiftoolExecutable.equals("")) { + this.runtimeExiftoolExecutable = runtimeExiftoolExecutable; + } else { + this.runtimeExiftoolExecutable = null; + } + this.passthroughXmpProperties = null; + } + + public AbstractExiftoolMetadataExtractor(Metadata metadata, Set supportedTypes, + String runtimeExiftoolExecutable, Collection passthroughXmpProperties) { + this.metadata = metadata; + this.supportedTypes = supportedTypes; + if (runtimeExiftoolExecutable != null && !runtimeExiftoolExecutable.equals("")) { + this.runtimeExiftoolExecutable = runtimeExiftoolExecutable; + } else { + this.runtimeExiftoolExecutable = null; + } + this.passthroughXmpProperties = passthroughXmpProperties; + } + + /** + * Gets an ExifTool {@link ExternalParser} to do the work of extracting the metadata in the + * image into XML format. + * + * @return an ExifTool ExternalParser + */ + protected Parser getFileParser() { + ExternalParser parser = new ExternalParser(); + parser.setCommand(new String[] { + ExecutableUtils.getExiftoolExecutable(runtimeExiftoolExecutable), + "-X", + ExternalParser.INPUT_FILE_TOKEN }); + parser.setSupportedTypes(supportedTypes); + return parser; + } + + /** + * Gets a parser responsible for extracting metadata from the XML output of the ExifTool external parser. + * + * @return the XML parser + */ + protected abstract Parser getXmlResponseParser(); + + /** + * Parsers the given input stream for XML output then sends it + * to an XML response parser to set metadata values. + * + * @param stream + * @throws IOException + * @throws SAXException + * @throws TikaException + */ + public void parse(InputStream stream) throws IOException, + SAXException, TikaException { + + ParseContext fileContext = new ParseContext(); + Parser fileParser = getFileParser(); + fileContext.set(Parser.class, fileParser); + + ByteArrayOutputStream result = new ByteArrayOutputStream(); + OutputStreamWriter outputWriter = new OutputStreamWriter(result, DEFAULT_CHARSET); + ContentHandler fileHandler = new BodyContentHandler(outputWriter); + + if (logger.isDebugEnabled()) { + logger.debug("parsing file for XML output"); + } + + try { + fileParser.parse(stream, fileHandler, metadata, fileContext); + + if (logger.isTraceEnabled()) { + logger.trace("XML output:\n" + result.toString(DEFAULT_CHARSET)); + } + + ByteArrayInputStream xmlInputStream = new ByteArrayInputStream(result.toByteArray()); + + ParseContext xmlResponseContext = new ParseContext(); + Parser xmlResponseParser = getXmlResponseParser(); + xmlResponseContext.set(Parser.class, xmlResponseParser); + ContentHandler xmlResponseHandler = new BodyContentHandler(); + + if (logger.isDebugEnabled()) { + logger.debug("parsing XML output for metadata"); + } + xmlResponseParser.parse(xmlInputStream, xmlResponseHandler, metadata, xmlResponseContext); + } catch (IOException e) { + if (logger.isWarnEnabled()) { + logger.warn("exiftool may not be be available: " + e.getMessage()); + } + } + } + + /** + * Determines if the given exiftoolPropertyName is a valid XMP property + * for use with ExifTool. + * + * @param exiftoolPropertyName + * @return whether or not the property is XMP + */ + public static boolean isValidXmpPropertyName(String exiftoolPropertyName) { + return (exiftoolPropertyName.startsWith(ExifToolMetadata.PREFIX_XMP) && + exiftoolPropertyName.contains(ExifToolMetadata.PREFIX_DELIMITER)); + } + + /** + * Constructs a full {@link QName} from the given exiftoolProperty + * + * @param exiftoolProperty + * @return the full QName for the property + */ + public QName getQName(Property exiftoolProperty) { + String prefix = null; + String namespaceUrl = null; + String name = null; + if (exiftoolProperty.getName().startsWith(ExifToolMetadata.PREFIX_XMP_DC)) { + prefix = ExifToolMetadata.PREFIX_XMP_DC; + namespaceUrl = ExifToolMetadata.NAMESPACE_URI_XMP_DC; + } else if (exiftoolProperty.getName().startsWith(ExifToolMetadata.PREFIX_XMP_XMP_RIGHTS)) { + prefix = ExifToolMetadata.PREFIX_XMP_XMP_RIGHTS; + namespaceUrl = ExifToolMetadata.NAMESPACE_URI_XMP_XMP_RIGHTS; + } + if (prefix != null && namespaceUrl != null) { + name = exiftoolProperty.getName().replace(prefix + ExifToolMetadata.PREFIX_DELIMITER, ""); + } else { + if (passthroughXmpProperties != null && isValidXmpPropertyName(exiftoolProperty.getName()) && + passthroughXmpProperties.contains(exiftoolProperty)) { + prefix = exiftoolProperty.getName().split(ExifToolMetadata.PREFIX_DELIMITER)[0]; + namespaceUrl = FORMAT_EXIFTOOL_XMP_NAMESPACE.format(new Object[] { prefix }); + name = exiftoolProperty.getName().split(ExifToolMetadata.PREFIX_DELIMITER)[1]; + } + } + if (prefix != null && namespaceUrl != null && name != null) { + return new QName(namespaceUrl, name, prefix); + } + return null; + } + + /** + * Extension of XMLParser which provides a {@link ContentHandler} which + * recognizes ExifTool's namespaces and elements. + * + * @author rgauss + * + */ + public abstract class AbstractExiftoolXmlParser extends XMLParser { + + private static final long serialVersionUID = 4937561608422796636L; + + private ExiftoolTikaMapper exiftoolTikaMapper; + + public AbstractExiftoolXmlParser(ExiftoolTikaMapper exiftoolTikaMapper) { + super(); + this.exiftoolTikaMapper = exiftoolTikaMapper; + } + + public ExiftoolTikaMapper getExiftoolTikaMapper() { + return exiftoolTikaMapper; + } + + public void setExiftoolTikaMapper(ExiftoolTikaMapper exiftoolTikaMapper) { + this.exiftoolTikaMapper = exiftoolTikaMapper; + } + + @Override + protected ContentHandler getContentHandler( + ContentHandler handler, Metadata metadata, ParseContext context) { + if (passthroughXmpProperties == null) { + return super.getContentHandler(handler, metadata, context); + } + + Set contentHandlers = new HashSet(); + contentHandlers.add(super.getContentHandler(handler, metadata, context)); + + for (Property property : passthroughXmpProperties) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + + return new TeeContentHandler(contentHandlers.toArray(new ContentHandler[] {})); + } + + /** + * Gets an element handler for the given Property and QName. + * + * @param property + * @param metadata + * @param qName + * @param tikaMetadata + * @return + */ + protected ContentHandler getElementMetadataHandler( + Property property, Metadata metadata, QName qName, Object tikaMetadata) { + if (tikaMetadata != null && tikaMetadata instanceof Property) { + return new ElementMetadataHandler( + qName.getNamespaceURI(), + qName.getLocalPart(), + metadata, + (Property) tikaMetadata, + true, + true); + } else { + return new ElementMetadataHandler( + qName.getNamespaceURI(), + qName.getLocalPart(), + metadata, + (String) tikaMetadata, + true, + true); + } + } + + /** + * Gets the element handlers for the given property + * + * @param property + * @param metadata + * @return the element handler + */ + protected Collection getElementMetadataHandlers(Property property, Metadata metadata) { + ArrayList handlers = new ArrayList(); + QName qName = getQName(property); + if (qName != null) { + if (getExiftoolTikaMapper().getExiftoolToTikaMetadataMap().get(property) != null) { + for (Object tikaMetadata : getExiftoolTikaMapper().getExiftoolToTikaMetadataMap().get(property)) { + handlers.add(getElementMetadataHandler(property, metadata, qName, tikaMetadata)); + } + } else { + if (logger.isTraceEnabled()) { + logger.trace("No tikaMetadata mapping found for " + property.getName() + ", passing through as " + qName.toString()); + } + handlers.add(getElementMetadataHandler(property, metadata, qName, property)); + } + } + return handlers; + } + + } + +} diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java new file mode 100644 index 00000000000..d7f0f0de1af --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Class for getting the exiftool command line executable + * path from properties or an override parameter. + * + * @author rgauss + */ +public class ExecutableUtils { + /** + * Logger for this class + */ + private static final Log logger = LogFactory.getLog(ExecutableUtils.class); + + private static final String DEFAULT_EXIFTOOL_EXECUTABLE = "exiftool"; + + private static final String PROPERTIES_OVERRIDE_FILE = "tika.exiftool.override.properties"; + private static final String PROPERTIES_FILE = "tika.exiftool.properties"; + private static final String PROPERTY_EXIFTOOL_EXECUTABLE = "exiftool.executable"; + + /** + * Gets the command line executable path for exiftool. + * + * If the runtimeExiftoolExecutable parameter is not null that is returned. + * If "org/apache/tika/parser/exiftool/tika.exiftool.override.properties" is found on the classpath + * the value for "exiftool.executable" is returned. + * Otherwise the value from "org/apache/tika/parser/exiftool/tika.exiftool.properties" + * for "exiftool.executable" is returned. + * + * @param runtimeExiftoolExecutable + * @return the exiftool executable path + */ + public static final String getExiftoolExecutable(String runtimeExiftoolExecutable) { + if (runtimeExiftoolExecutable != null) { + return runtimeExiftoolExecutable; + } + String executable = DEFAULT_EXIFTOOL_EXECUTABLE; + InputStream stream; + stream = ExecutableUtils.class.getResourceAsStream(PROPERTIES_OVERRIDE_FILE); + if (stream == null) { + stream = ExecutableUtils.class.getResourceAsStream(PROPERTIES_FILE); + } + if(stream != null){ + try { + Properties props = new Properties(); + props.load(stream); + executable = (String) props.get(PROPERTY_EXIFTOOL_EXECUTABLE); + } catch (IOException e) { + logger.warn("IOException while trying to load property file. Message: " + e.getMessage()); + } + } + return executable; + } + +} diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java new file mode 100644 index 00000000000..2476b1c4914 --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import org.apache.tika.metadata.Property; + +/** + * A collection of ExifTool metadata names. + * + * @author rgauss + * @see ExifTool Tag Names + */ +public interface ExifToolMetadata { + + String NAMESPACE_URI_IPTC = "http://ns.exiftool.ca/IPTC/IPTC/1.0/"; + String NAMESPACE_URI_XMP_DC = "http://ns.exiftool.ca/XMP/XMP-dc/1.0/"; + String NAMESPACE_URI_XMP_IPTC_CORE = "http://ns.exiftool.ca/XMP/XMP-iptcCore/1.0/"; + String NAMESPACE_URI_XMP_IPTC_EXT = "http://ns.exiftool.ca/XMP/XMP-iptcExt/1.0/"; + String NAMESPACE_URI_XMP_PHOTOSHOP = "http://ns.exiftool.ca/XMP/XMP-photoshop/1.0/"; + String NAMESPACE_URI_XMP_PLUS = "http://ns.exiftool.ca/XMP/XMP-plus/1.0/"; + String NAMESPACE_URI_XMP_X = "http://ns.exiftool.ca/XMP/XMP-x/1.0/"; + String NAMESPACE_URI_XMP_XMP = "http://ns.exiftool.ca/XMP/XMP-xmp/1.0/"; + String NAMESPACE_URI_XMP_XMP_RIGHTS = "http://ns.exiftool.ca/XMP/XMP-xmpRights/1.0/"; + + String PREFIX_IPTC = "IPTC"; + String PREFIX_XMP = "XMP-"; + String PREFIX_XMP_DC = "XMP-dc"; + String PREFIX_XMP_IPTC_CORE = "XMP-iptcCore"; + String PREFIX_XMP_IPTC_EXT = "XMP-iptcExt"; + String PREFIX_XMP_PHOTOSHOP = "XMP-photoshop"; + String PREFIX_XMP_PLUS = "XMP-plus"; + String PREFIX_XMP_X = "XMP-x"; + String PREFIX_XMP_XMP = "XMP-xmp"; + String PREFIX_XMP_XMP_RIGHTS = "XMP-xmpRights"; + + String PREFIX_DELIMITER = ":"; + + Property IPTC_BY_LINE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "By-line"); + Property IPTC_BY_LINE_TITLE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "By-lineTitle"); + Property IPTC_CAPTION = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Caption-Abstract"); + Property IPTC_CATEGORY = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Category"); + Property IPTC_CITY = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "City"); + Property IPTC_COPYRIGHT_NOTICE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "CopyrightNotice"); + Property IPTC_COUNTRY_PRIMARY_LOCATION_CODE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Country-PrimaryLocationCode"); + Property IPTC_COUNTRY_PRIMARY_LOCATION_NAME = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Country-PrimaryLocationName"); + Property IPTC_CREDIT = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Credit"); + Property IPTC_DATE_CREATED = Property.internalDate( + PREFIX_IPTC + PREFIX_DELIMITER + "DateCreated"); + Property IPTC_HEADLINE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Headline"); + Property IPTC_KEYWORDS = Property.internalTextBag( + PREFIX_IPTC + PREFIX_DELIMITER + "Keywords"); + Property IPTC_OBJECT_NAME = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "ObjectName"); + Property IPTC_OBJECT_ATTRIBUTE_REFERENCE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "ObjectAttributeReference"); + Property IPTC_ORIGINAL_TRANSMISSION_REFERENCE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "OriginalTransmissionReference"); + Property IPTC_PROVINCE_STATE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Province-State"); + Property IPTC_SOURCE = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Source"); + Property IPTC_SPECIAL_INSTRUCTIONS = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "SpecialInstructions"); + Property IPTC_SUBLOCATION = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Sub-location"); + Property IPTC_SUPPLEMENTAL_CATEGORIES = Property.internalTextBag( + PREFIX_IPTC + PREFIX_DELIMITER + "SupplementalCategories"); + Property IPTC_URGENCY = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Urgency"); + Property IPTC_WRITER_EDITOR = Property.internalText( + PREFIX_IPTC + PREFIX_DELIMITER + "Writer-Editor"); + + Property XMP_DC_CONTRIBUTOR = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Contributor"); + Property XMP_DC_COVERAGE = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Coverage"); + Property XMP_DC_CREATOR = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Creator"); + Property XMP_DC_DATE = Property.internalDate( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Date"); + Property XMP_DC_DESCRIPTION = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Description"); + Property XMP_DC_FORMAT = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Format"); + Property XMP_DC_IDENTIFIER = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Identifier"); + Property XMP_DC_LANGUAGE = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Language"); + Property XMP_DC_PUBLISHER = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Publisher"); + Property XMP_DC_RELATION = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Relation"); + Property XMP_DC_RIGHTS = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Rights"); + Property XMP_DC_SOURCE = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Source"); + Property XMP_DC_SUBJECT = Property.internalTextBag( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Subject"); + Property XMP_DC_TITLE = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Title"); + Property XMP_DC_TYPE = Property.internalText( + PREFIX_XMP_DC + PREFIX_DELIMITER + "Type"); + + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_CITY = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorCity"); + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_COUNTRY = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorCountry"); + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_ADDRESS = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorAddress"); + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_POSTAL_CODE = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorPostalCode"); + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_REGION = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorRegion"); + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_EMAIL_WORK = Property.internalTextBag( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorWorkEmail"); + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_TELEPHONE_WORK = Property.internalTextBag( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorWorkTelephone"); + Property XMP_IPTCCORE_CREATOR_CONTACT_INFO_URL_WORK = Property.internalTextBag( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CreatorWorkURL"); + Property XMP_IPTCCORE_COUNTRY_CODE = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "CountryCode"); + Property XMP_IPTCCORE_INTELLECTUAL_GENRE = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "IntellectualGenre"); + Property XMP_IPTCCORE_LOCATION = Property.internalText( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "Location"); + Property XMP_IPTCCORE_SCENE = Property.internalTextBag( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "Scene"); + Property XMP_IPTCCORE_SUBJECT_CODE = Property.internalTextBag( + PREFIX_XMP_IPTC_CORE + PREFIX_DELIMITER + "SubjectCode"); + + Property XMP_IPTCEXT_ADDITIONAL_MODEL_INFORMATION = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "AdditionalModelInformation"); + Property XMP_IPTCEXT_ARTWORK_OR_OBJECT_COPYRIGHT_NOTICE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ArtworkCopyrightNotice"); + Property XMP_IPTCEXT_ARTWORK_OR_OBJECT_CREATOR = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ArtworkCreator"); + Property XMP_IPTCEXT_ARTWORK_OR_OBJECT_DATE_CREATED = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ArtworkDateCreated"); + Property XMP_IPTCEXT_ARTWORK_OR_OBJECT_SOURCE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ArtworkSource"); + Property XMP_IPTCEXT_ARTWORK_OR_OBJECT_SOURCE_INVENTORY_NO = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ArtworkSourceInventoryNo"); + Property XMP_IPTCEXT_ARTWORK_OR_OBJECT_TITLE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ArtworkTitle"); + Property XMP_IPTCEXT_CONTROLLED_VOCABULAR_TERM = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ControlledVocabularyTerm"); + Property XMP_IPTCEXT_DIGITAL_IMAGE_GUID = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "DigitalImageGUID"); + Property XMP_IPTCEXT_DIGITAL_SOURCE_TYPE = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "DigitalSourceType"); + Property XMP_IPTCEXT_EVENT = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "Event"); + Property XMP_IPTCEXT_IPTC_LAST_EDITED = Property.internalDate( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "IptcLastEdited"); + Property XMP_IPTCEXT_LOCATION_CREATED_CITY = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationCreatedCity"); + Property XMP_IPTCEXT_LOCATION_CREATED_COUNTRY_CODE = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationCreatedCountryCode"); + Property XMP_IPTCEXT_LOCATION_CREATED_COUNTRY_NAME = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationCreatedCountryName"); + Property XMP_IPTCEXT_LOCATION_CREATED_PROVINCE_STATE = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationCreatedProvinceState"); + Property XMP_IPTCEXT_LOCATION_CREATED_SUBLOCATION = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationCreatedSublocation"); + Property XMP_IPTCEXT_LOCATION_CREATED_WORLD_REGION = Property.internalText( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationCreatedWorldRegion"); + Property XMP_IPTCEXT_LOCATION_SHOWN_CITY = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationShownCity"); + Property XMP_IPTCEXT_LOCATION_SHOWN_COUNTRY_CODE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationShownCountryCode"); + Property XMP_IPTCEXT_LOCATION_SHOWN_COUNTRY_NAME = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationShownCountryName"); + Property XMP_IPTCEXT_LOCATION_SHOWN_PROVINCE_STATE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationShownProvinceState"); + Property XMP_IPTCEXT_LOCATION_SHOWN_SUBLOCATION = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationShownSublocation"); + Property XMP_IPTCEXT_LOCATION_SHOWN_WORLD_REGION = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "LocationShownWorldRegion"); + Property XMP_IPTCEXT_MAX_AVAIL_HEIGHT = Property.internalInteger( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "MaxAvailHeight"); + Property XMP_IPTCEXT_MAX_AVAIL_WIDTH = Property.internalInteger( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "MaxAvailWidth"); + Property XMP_IPTCEXT_MODEL_AGE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "ModelAge"); + Property XMP_IPTCEXT_ORGANISATION_IN_IMAGE_CODE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "OrganisationInImageCode"); + Property XMP_IPTCEXT_ORGANISATION_IN_IMAGE_NAME = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "OrganisationInImageName"); + Property XMP_IPTCEXT_PERSON_IN_IMAGE = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "PersonInImage"); + Property XMP_IPTCEXT_REGISTRY_ITEM_ID = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "RegistryItemID"); + Property XMP_IPTCEXT_REGISTRY_ORGANISATION_ID = Property.internalTextBag( + PREFIX_XMP_IPTC_EXT + PREFIX_DELIMITER + "RegistryOrganisationID"); + + Property XMP_PHOTOSHOP_AUTHORS_POSITION = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "AuthorsPosition"); + Property XMP_PHOTOSHOP_CAPTION_WRITER = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "CaptionWriter"); + Property XMP_PHOTOSHOP_CATEGORY = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "Category"); + Property XMP_PHOTOSHOP_CITY = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "City"); + Property XMP_PHOTOSHOP_COUNTRY = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "Country"); + Property XMP_PHOTOSHOP_CREDIT = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "Credit"); + Property XMP_PHOTOSHOP_DATE_CREATED = Property.internalDate( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "DateCreated"); + Property XMP_PHOTOSHOP_HEADLINE = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "Headline"); + Property XMP_PHOTOSHOP_HISTORY = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "History"); + Property XMP_PHOTOSHOP_INSTRUCTIONS = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "Instructions"); + Property XMP_PHOTOSHOP_LEGACY_IPTC_DIGEST = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "LegacyIPTCDigest"); + Property XMP_PHOTOSHOP_SIDECAR_FOR_EXTENSION = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "SidecarForExtension"); + Property XMP_PHOTOSHOP_SOURCE = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "Source"); + Property XMP_PHOTOSHOP_STATE = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "State"); + Property XMP_PHOTOSHOP_SUPPLEMENTAL_CATEGORIES = Property.internalTextBag( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "SupplementalCategories"); + Property XMP_PHOTOSHOP_TRANSMISSION_REFERENCE = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "TransmissionReference"); + Property XMP_PHOTOSHOP_URGENCY = Property.internalText( + PREFIX_XMP_PHOTOSHOP + PREFIX_DELIMITER + "Urgency"); + + Property XMP_PLUS_IMAGE_SUPPLIER_IMAGE_ID = Property.internalText( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "ImageSupplierImageID"); + Property XMP_PLUS_IMAGE_SUPPLIER_ID = Property.internalText( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "ImageSupplierID"); + Property XMP_PLUS_IMAGE_SUPPLIER_NAME = Property.internalText( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "ImageSupplierName"); + Property XMP_PLUS_VERSION = Property.internalText( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "PLUSVersion"); + Property XMP_PLUS_COPYRIGHT_OWNER_ID = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "CopyrightOwnerID"); + Property XMP_PLUS_COPYRIGHT_OWNER_NAME = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "CopyrightOwnerName"); + Property XMP_PLUS_IMAGE_CREATOR_ID = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "ImageCreatorID"); + Property XMP_PLUS_IMAGE_CREATOR_NAME = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "ImageCreatorName"); + Property XMP_PLUS_LICENSOR_ID = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorID"); + Property XMP_PLUS_LICENSOR_NAME = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorName"); + Property XMP_PLUS_LICENSOR_CITY = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorCity"); + Property XMP_PLUS_LICENSOR_COUNTRY = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorCountry"); + Property XMP_PLUS_LICENSOR_EMAIL = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorEmail"); + Property XMP_PLUS_LICENSOR_EXTENDED_ADDRESS = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorExtendedAddress"); + Property XMP_PLUS_LICENSOR_POSTAL_CODE = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorPostalCode"); + Property XMP_PLUS_LICENSOR_REGION = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorRegion"); + Property XMP_PLUS_LICENSOR_STREET_ADDRESS = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorStreetAddress"); + Property XMP_PLUS_LICENSOR_TELEPHONE_1 = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorTelephone1"); + Property XMP_PLUS_LICENSOR_TELEPHONE_2 = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorTelephone2"); + Property XMP_PLUS_LICENSOR_URL = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "LicensorURL"); + Property XMP_PLUS_MINOR_MODEL_AGE_DISCLOSURE = Property.internalText( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "MinorModelAgeDisclosure"); + Property XMP_PLUS_MODEL_RELEASE_ID = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "ModelReleaseID"); + Property XMP_PLUS_MODEL_RELEASE_STATUS = Property.internalText( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "ModelReleaseStatus"); + Property XMP_PLUS_PROPERTY_RELEASE_ID = Property.internalTextBag( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "PropertyReleaseID"); + Property XMP_PLUS_PROPERTY_RELEASE_STATUS = Property.internalText( + PREFIX_XMP_PLUS + PREFIX_DELIMITER + "PropertyReleaseStatus"); + + Property XMP_XMP_BASE_URL = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "BaseURL"); + Property XMP_XMP_CREATE_DATE = Property.internalDate( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "CreateDate"); + Property XMP_XMP_CREATOR_TOOL = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "CreatorTool"); + Property XMP_XMP_DESCRIPTION = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Description"); + Property XMP_XMP_FORMAT = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Format"); + Property XMP_XMP_IDENTIFIER = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Identifier"); + Property XMP_XMP_KEYWORDS = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Keywords"); + Property XMP_XMP_LABEL = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Label"); + Property XMP_XMP_METADATA_DATE = Property.internalDate( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "MetadataDate"); + Property XMP_XMP_MODIFY_DATE = Property.internalDate( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "ModifyDate"); + Property XMP_XMP_NICKNAME = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Nickname"); + Property XMP_XMP_RATING = Property.internalReal( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Rating"); + Property XMP_XMP_TITLE = Property.internalText( + PREFIX_XMP_XMP + PREFIX_DELIMITER + "Title"); + + Property XMP_XMPRIGHTS_CERTIFICATE = Property.internalText( + PREFIX_XMP_XMP_RIGHTS + PREFIX_DELIMITER + "Certificate"); + Property XMP_XMPRIGHTS_MARKED = Property.internalBoolean( + PREFIX_XMP_XMP_RIGHTS + PREFIX_DELIMITER + "Marked"); + Property XMP_XMPRIGHTS_OWNER = Property.internalText( + PREFIX_XMP_XMP_RIGHTS + PREFIX_DELIMITER + "Owner"); + Property XMP_XMPRIGHTS_USAGE_TERMS = Property.internalText( + PREFIX_XMP_XMP_RIGHTS + PREFIX_DELIMITER + "UsageTerms"); + Property XMP_XMPRIGHTS_WEB_STATEMENT = Property.internalText( + PREFIX_XMP_XMP_RIGHTS + PREFIX_DELIMITER + "WebStatement"); + + Property[] PROPERTY_GROUP_IPTC = new Property[] { + IPTC_BY_LINE, + IPTC_BY_LINE_TITLE, + IPTC_CAPTION, + IPTC_CITY, + IPTC_COPYRIGHT_NOTICE, + IPTC_COUNTRY_PRIMARY_LOCATION_NAME, + IPTC_CREDIT, + IPTC_DATE_CREATED, + IPTC_HEADLINE, + IPTC_KEYWORDS, + IPTC_OBJECT_NAME, + IPTC_ORIGINAL_TRANSMISSION_REFERENCE, + IPTC_PROVINCE_STATE, + IPTC_SOURCE, + IPTC_SPECIAL_INSTRUCTIONS, + IPTC_SUPPLEMENTAL_CATEGORIES, + IPTC_WRITER_EDITOR + }; + + Property[] PROPERTY_GROUP_XMP_DC = new Property[] { + XMP_DC_CONTRIBUTOR, + XMP_DC_COVERAGE, + XMP_DC_CREATOR, + XMP_DC_DATE, + XMP_DC_DESCRIPTION, + XMP_DC_FORMAT, + XMP_DC_IDENTIFIER, + XMP_DC_LANGUAGE, + XMP_DC_PUBLISHER, + XMP_DC_RELATION, + XMP_DC_RIGHTS, + XMP_DC_SOURCE, + XMP_DC_SUBJECT, + XMP_DC_TITLE, + XMP_DC_TYPE + }; + + Property[] PROPERTY_GROUP_XMP_IPTC_CORE = new Property[] { + XMP_IPTCCORE_CREATOR_CONTACT_INFO_CITY, + XMP_IPTCCORE_CREATOR_CONTACT_INFO_COUNTRY, + XMP_IPTCCORE_CREATOR_CONTACT_INFO_ADDRESS, + XMP_IPTCCORE_CREATOR_CONTACT_INFO_POSTAL_CODE, + XMP_IPTCCORE_CREATOR_CONTACT_INFO_REGION, + XMP_IPTCCORE_CREATOR_CONTACT_INFO_EMAIL_WORK, + XMP_IPTCCORE_CREATOR_CONTACT_INFO_TELEPHONE_WORK, + XMP_IPTCCORE_CREATOR_CONTACT_INFO_URL_WORK, + XMP_IPTCCORE_COUNTRY_CODE, + XMP_IPTCCORE_INTELLECTUAL_GENRE, + XMP_IPTCCORE_LOCATION, + XMP_IPTCCORE_SCENE, + XMP_IPTCCORE_SUBJECT_CODE + }; + + Property[] PROPERTY_GROUP_XMP_IPTC_EXT = new Property[] { + XMP_IPTCEXT_ADDITIONAL_MODEL_INFORMATION, + XMP_IPTCEXT_ARTWORK_OR_OBJECT_COPYRIGHT_NOTICE, + XMP_IPTCEXT_ARTWORK_OR_OBJECT_CREATOR, + XMP_IPTCEXT_ARTWORK_OR_OBJECT_DATE_CREATED, + XMP_IPTCEXT_ARTWORK_OR_OBJECT_SOURCE, + XMP_IPTCEXT_ARTWORK_OR_OBJECT_SOURCE_INVENTORY_NO, + XMP_IPTCEXT_ARTWORK_OR_OBJECT_TITLE, + XMP_IPTCEXT_CONTROLLED_VOCABULAR_TERM, + XMP_IPTCEXT_DIGITAL_IMAGE_GUID, + XMP_IPTCEXT_DIGITAL_SOURCE_TYPE, + XMP_IPTCEXT_EVENT, + XMP_IPTCEXT_IPTC_LAST_EDITED, + XMP_IPTCEXT_LOCATION_CREATED_CITY, + XMP_IPTCEXT_LOCATION_CREATED_COUNTRY_CODE, + XMP_IPTCEXT_LOCATION_CREATED_COUNTRY_NAME, + XMP_IPTCEXT_LOCATION_CREATED_PROVINCE_STATE, + XMP_IPTCEXT_LOCATION_CREATED_SUBLOCATION, + XMP_IPTCEXT_LOCATION_CREATED_WORLD_REGION, + XMP_IPTCEXT_LOCATION_SHOWN_CITY, + XMP_IPTCEXT_LOCATION_SHOWN_COUNTRY_CODE, + XMP_IPTCEXT_LOCATION_SHOWN_COUNTRY_NAME, + XMP_IPTCEXT_LOCATION_SHOWN_PROVINCE_STATE, + XMP_IPTCEXT_LOCATION_SHOWN_SUBLOCATION, + XMP_IPTCEXT_LOCATION_SHOWN_WORLD_REGION, + XMP_IPTCEXT_MAX_AVAIL_HEIGHT, + XMP_IPTCEXT_MAX_AVAIL_WIDTH, + XMP_IPTCEXT_MODEL_AGE, + XMP_IPTCEXT_ORGANISATION_IN_IMAGE_CODE, + XMP_IPTCEXT_ORGANISATION_IN_IMAGE_NAME, + XMP_IPTCEXT_PERSON_IN_IMAGE, + XMP_IPTCEXT_REGISTRY_ITEM_ID, + XMP_IPTCEXT_REGISTRY_ORGANISATION_ID + }; + + Property[] PROPERTY_GROUP_XMP_PHOTOSHOP = new Property[] { + XMP_PHOTOSHOP_AUTHORS_POSITION, + XMP_PHOTOSHOP_CAPTION_WRITER, + XMP_PHOTOSHOP_CATEGORY, + XMP_PHOTOSHOP_CITY, + XMP_PHOTOSHOP_COUNTRY, + XMP_PHOTOSHOP_CREDIT, + XMP_PHOTOSHOP_DATE_CREATED, + XMP_PHOTOSHOP_HEADLINE, + XMP_PHOTOSHOP_HISTORY, + XMP_PHOTOSHOP_INSTRUCTIONS, + XMP_PHOTOSHOP_LEGACY_IPTC_DIGEST, + XMP_PHOTOSHOP_SIDECAR_FOR_EXTENSION, + XMP_PHOTOSHOP_SOURCE, + XMP_PHOTOSHOP_STATE, + XMP_PHOTOSHOP_SUPPLEMENTAL_CATEGORIES, + XMP_PHOTOSHOP_TRANSMISSION_REFERENCE, + XMP_PHOTOSHOP_URGENCY + }; + + Property[] PROPERTY_GROUP_XMP_PLUS = new Property[] { + XMP_PLUS_COPYRIGHT_OWNER_ID, + XMP_PLUS_COPYRIGHT_OWNER_NAME, + XMP_PLUS_IMAGE_CREATOR_ID, + XMP_PLUS_IMAGE_CREATOR_NAME, + XMP_PLUS_IMAGE_SUPPLIER_ID, + XMP_PLUS_IMAGE_SUPPLIER_IMAGE_ID, + XMP_PLUS_IMAGE_SUPPLIER_NAME, + XMP_PLUS_LICENSOR_CITY, + XMP_PLUS_LICENSOR_COUNTRY, + XMP_PLUS_LICENSOR_EMAIL, + XMP_PLUS_LICENSOR_EXTENDED_ADDRESS, + XMP_PLUS_LICENSOR_ID, + XMP_PLUS_LICENSOR_NAME, + XMP_PLUS_LICENSOR_POSTAL_CODE, + XMP_PLUS_LICENSOR_REGION, + XMP_PLUS_LICENSOR_STREET_ADDRESS, + XMP_PLUS_LICENSOR_TELEPHONE_1, + XMP_PLUS_LICENSOR_TELEPHONE_2, + XMP_PLUS_LICENSOR_URL, + XMP_PLUS_MINOR_MODEL_AGE_DISCLOSURE, + XMP_PLUS_MODEL_RELEASE_ID, + XMP_PLUS_MODEL_RELEASE_STATUS, + XMP_PLUS_PROPERTY_RELEASE_ID, + XMP_PLUS_PROPERTY_RELEASE_STATUS, + XMP_PLUS_VERSION + }; + + Property[] PROPERTY_GROUP_XMP_XMP = new Property[] { + XMP_XMP_BASE_URL, + XMP_XMP_CREATE_DATE, + XMP_XMP_CREATOR_TOOL, + XMP_XMP_DESCRIPTION, + XMP_XMP_FORMAT, + XMP_XMP_IDENTIFIER, + XMP_XMP_KEYWORDS, + XMP_XMP_LABEL, + XMP_XMP_METADATA_DATE, + XMP_XMP_MODIFY_DATE, + XMP_XMP_NICKNAME, + XMP_XMP_RATING, + XMP_XMP_TITLE + }; + + Property[] PROPERTY_GROUP_XMP_XMPRIGHTS = new Property[] { + XMP_XMPRIGHTS_CERTIFICATE, + XMP_XMPRIGHTS_MARKED, + XMP_XMPRIGHTS_OWNER, + XMP_XMPRIGHTS_USAGE_TERMS, + XMP_XMPRIGHTS_WEB_STATEMENT + }; + + Property EXIF_COPYRIGHT = Property.internalText("EXIF:Copyright"); + +} + diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java new file mode 100644 index 00000000000..470926494b0 --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.tika.exception.TikaException; +import org.apache.tika.io.TemporaryResources; +import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.IPTC; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.Property; +import org.apache.tika.metadata.TikaCoreProperties; +import org.apache.tika.mime.MediaType; +import org.apache.tika.parser.AbstractParser; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.parser.image.ImageMetadataExtractor; +import org.apache.tika.parser.image.ImageParser; +import org.apache.tika.parser.image.xmp.JempboxExtractor; +import org.apache.tika.sax.XHTMLContentHandler; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * A parser which uses the {@link ExiftoolIptcMetadataExtractor} to perform the work of metadata extraction. + * + * @author rgauss + * + */ +public class ExiftoolImageParser extends AbstractParser { + + private static final long serialVersionUID = 1469157231567542637L; + + private String exiftoolExecutable; + private Collection passthroughXmpProperties; + + private static final Set SUPPORTED_TYPES = + Collections.unmodifiableSet(new HashSet(Arrays.asList( + MediaType.image("jpeg"), + MediaType.image("png"), + MediaType.image("tiff")))); + + public Set getSupportedTypes(ParseContext context) { + // TODO Check for availability of command line before returning? + return SUPPORTED_TYPES; + } + + public ExiftoolImageParser() { + super(); + } + + public ExiftoolImageParser(String exiftoolExecutable) { + super(); + this.exiftoolExecutable = exiftoolExecutable; + } + + public ExiftoolImageParser(String exiftoolExecutable, Collection passthroughXmpProperties) { + super(); + this.exiftoolExecutable = exiftoolExecutable; + this.passthroughXmpProperties = passthroughXmpProperties; + } + + public void parse(InputStream stream, ContentHandler handler, + Metadata metadata, ParseContext context) throws IOException, + SAXException, TikaException { + TemporaryResources tmp = new TemporaryResources(); + try { + TikaInputStream tis = TikaInputStream.get(stream, tmp); + MediaType mediaType = MediaType.parse(metadata.get(Metadata.CONTENT_TYPE)); + if (mediaType != null && mediaType.equals(MediaType.image("jpeg"))) { + new ImageMetadataExtractor(metadata).parseJpeg(tis.getFile()); + } + if(mediaType != null && mediaType.equals(MediaType.image("tiff"))) { + new ImageMetadataExtractor(metadata).parseTiff(tis.getFile()); + } + tis.mark(Integer.MAX_VALUE); + ImageParser imageParser = new ImageParser(); + if (imageParser.getSupportedTypes(context).contains(mediaType)) { + imageParser.parse(tis, handler, metadata, context); + tis.reset(); + } + + // JempboxExtractor joins creators with comma, we want to preserve as is + String iptcCreator = metadata.get(IPTC.CREATOR); + + new JempboxExtractor(metadata).parse(tis); + tis.reset(); + + metadata.set(IPTC.CREATOR, iptcCreator); + + new ExiftoolIptcMetadataExtractor( + metadata, getSupportedTypes(context), exiftoolExecutable, passthroughXmpProperties).parse(tis); + } finally { + tmp.dispose(); + } + + XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata); + xhtml.startDocument(); + xhtml.endDocument(); + + cleanDuplicateKeywords(metadata); + } + + /** + * The {@link JempboxExtractor} calls + * metadata.add(TikaCoreProperties.KEYWORDS, keywords.next()); + * for each keyword and metadata.add does not check for duplicates. + * The exiftool XML parsing may add them again, so we must forcibly remove duplicates + * using this method. + * + * @param metadata + */ + protected void cleanDuplicateKeywords(Metadata metadata) { + String[] keywords = metadata.getValues(TikaCoreProperties.KEYWORDS); + if (keywords.length == 0) { + return; + } + LinkedHashSet cleanedKeywords = new LinkedHashSet(); + for (int i = 0; i < keywords.length; i++) + { + cleanedKeywords.add(keywords[i]); + } + metadata.set(TikaCoreProperties.KEYWORDS, cleanedKeywords.toArray(new String[] {})); + } + +} diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java new file mode 100644 index 00000000000..2adb5fe2f24 --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.namespace.QName; + +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.Property; +import org.apache.tika.mime.MediaType; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.parser.Parser; +import org.apache.tika.sax.TeeContentHandler; +import org.xml.sax.ContentHandler; + +/** + * An IPTC extractor implementation of AbstractExiftoolMetadataExtractor + * + */ +public class ExiftoolIptcMetadataExtractor extends AbstractExiftoolMetadataExtractor { + + + public ExiftoolIptcMetadataExtractor(Metadata metadata, Set supportedTypes) { + super(metadata, supportedTypes); + } + + public ExiftoolIptcMetadataExtractor(Metadata metadata, Set supportedTypes, String runtimeExiftoolExecutable) { + super(metadata, supportedTypes, runtimeExiftoolExecutable); + } + + public ExiftoolIptcMetadataExtractor(Metadata metadata, Set supportedTypes, String runtimeExiftoolExecutable, + Collection passthroughXmpProperties) { + super(metadata, supportedTypes, runtimeExiftoolExecutable, passthroughXmpProperties); + } + + /** + * Gets a parser responsible for extracting metadata from the XML output of the ExifTool external parser. + * + * @return the XML parser + */ + @Override + protected Parser getXmlResponseParser() { + return new ExiftoolIptcXmlParser(new ExiftoolTikaIptcMapper()); + } + + @Override + public QName getQName(Property exiftoolProperty) { + QName qName = super.getQName(exiftoolProperty); + if (qName != null) { + return qName; + } + + String prefix = null; + String namespaceUrl = null; + if (exiftoolProperty.getName().startsWith(ExifToolMetadata.PREFIX_IPTC)) { + prefix = ExifToolMetadata.PREFIX_IPTC; + namespaceUrl = ExifToolMetadata.NAMESPACE_URI_IPTC; + } else if (exiftoolProperty.getName().startsWith(ExifToolMetadata.PREFIX_XMP_IPTC_CORE)) { + prefix = ExifToolMetadata.PREFIX_XMP_IPTC_CORE; + namespaceUrl = ExifToolMetadata.NAMESPACE_URI_XMP_IPTC_CORE; + } else if (exiftoolProperty.getName().startsWith(ExifToolMetadata.PREFIX_XMP_IPTC_EXT)) { + prefix = ExifToolMetadata.PREFIX_XMP_IPTC_EXT; + namespaceUrl = ExifToolMetadata.NAMESPACE_URI_XMP_IPTC_EXT; + } else if (exiftoolProperty.getName().startsWith(ExifToolMetadata.PREFIX_XMP_PHOTOSHOP)) { + prefix = ExifToolMetadata.PREFIX_XMP_PHOTOSHOP; + namespaceUrl = ExifToolMetadata.NAMESPACE_URI_XMP_PHOTOSHOP; + } else if (exiftoolProperty.getName().startsWith(ExifToolMetadata.PREFIX_XMP_PLUS)) { + prefix = ExifToolMetadata.PREFIX_XMP_PLUS; + namespaceUrl = ExifToolMetadata.NAMESPACE_URI_XMP_PLUS; + } + if (prefix != null && namespaceUrl != null) { + return new QName(namespaceUrl, exiftoolProperty.getName().replace(prefix + ExifToolMetadata.PREFIX_DELIMITER, ""), prefix); + } + return null; + } + + /** + * Extension of XMLParser which provides a {@link ContentHandler} which + * recognizes ExifTool's namespaces and elements. + * + * @author rgauss + * + */ + public class ExiftoolIptcXmlParser extends AbstractExiftoolXmlParser { + + private static final long serialVersionUID = -8633426227113109506L; + + public ExiftoolIptcXmlParser(ExiftoolTikaMapper exiftoolTikaMapper) { + super(exiftoolTikaMapper); + } + + @Override + protected ContentHandler getContentHandler( + ContentHandler handler, Metadata metadata, ParseContext context) { + + Set contentHandlers = new HashSet(); + contentHandlers.add(super.getContentHandler(handler, metadata, context)); + for (Property property : ExifToolMetadata.PROPERTY_GROUP_IPTC) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + for (Property property : ExifToolMetadata.PROPERTY_GROUP_XMP_DC) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + for (Property property : ExifToolMetadata.PROPERTY_GROUP_XMP_IPTC_CORE) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + for (Property property : ExifToolMetadata.PROPERTY_GROUP_XMP_IPTC_EXT) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + for (Property property : ExifToolMetadata.PROPERTY_GROUP_XMP_PHOTOSHOP) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + for (Property property : ExifToolMetadata.PROPERTY_GROUP_XMP_PLUS) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + for (Property property : ExifToolMetadata.PROPERTY_GROUP_XMP_XMP) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + for (Property property : ExifToolMetadata.PROPERTY_GROUP_XMP_XMPRIGHTS) { + contentHandlers.addAll(getElementMetadataHandlers(property, metadata)); + } + + return new TeeContentHandler(contentHandlers.toArray(new ContentHandler[] {})); + } + } + +} diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java new file mode 100644 index 00000000000..e5f12e3d504 --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.tika.metadata.IPTC; +import org.apache.tika.metadata.Property; +import org.apache.tika.metadata.TikaCoreProperties; + +/** + * Class for mapping Tika IPTC metadata names to and from ExifTool metadata names. + * + * @author rgauss + * + */ +public class ExiftoolTikaIptcMapper implements ExiftoolTikaMapper { + + private static final Map> _tikaToExiftoolMetadataMap = new HashMap>(); + private static final Map> _exiftoolToTikaMetadataMap = new HashMap>(); + + static { + _tikaToExiftoolMetadataMap.put( + IPTC.CITY, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_CITY, + ExifToolMetadata.IPTC_CITY )); + _tikaToExiftoolMetadataMap.put( + IPTC.COUNTRY, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_COUNTRY, + ExifToolMetadata.IPTC_COUNTRY_PRIMARY_LOCATION_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.COUNTRY_CODE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_COUNTRY_CODE, + ExifToolMetadata.IPTC_COUNTRY_PRIMARY_LOCATION_CODE )); + _tikaToExiftoolMetadataMap.put( + IPTC.DESCRIPTION, + Arrays.asList( + ExifToolMetadata.XMP_DC_DESCRIPTION, + ExifToolMetadata.IPTC_CAPTION )); + _tikaToExiftoolMetadataMap.put( + IPTC.HEADLINE, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_HEADLINE, + ExifToolMetadata.IPTC_HEADLINE )); + _tikaToExiftoolMetadataMap.put( + IPTC.INTELLECTUAL_GENRE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_INTELLECTUAL_GENRE, + ExifToolMetadata.IPTC_OBJECT_ATTRIBUTE_REFERENCE )); + _tikaToExiftoolMetadataMap.put( + IPTC.KEYWORDS, + Arrays.asList( + ExifToolMetadata.XMP_DC_SUBJECT, + ExifToolMetadata.IPTC_KEYWORDS )); + _tikaToExiftoolMetadataMap.put( + IPTC.PROVINCE_OR_STATE, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_STATE, + ExifToolMetadata.IPTC_PROVINCE_STATE )); + _tikaToExiftoolMetadataMap.put( + IPTC.SCENE_CODE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_SCENE )); + _tikaToExiftoolMetadataMap.put( + IPTC.SUBJECT_CODE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_SUBJECT_CODE )); + _tikaToExiftoolMetadataMap.put( + IPTC.SUBLOCATION, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_LOCATION, + ExifToolMetadata.IPTC_SUBLOCATION )); + _tikaToExiftoolMetadataMap.put( + IPTC.DATE_CREATED, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_DATE_CREATED, + ExifToolMetadata.IPTC_DATE_CREATED )); + _tikaToExiftoolMetadataMap.put( + IPTC.DESCRIPTION_WRITER, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_CAPTION_WRITER, + ExifToolMetadata.IPTC_WRITER_EDITOR )); + _tikaToExiftoolMetadataMap.put( + IPTC.INSTRUCTIONS, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_INSTRUCTIONS, + ExifToolMetadata.IPTC_SPECIAL_INSTRUCTIONS )); + _tikaToExiftoolMetadataMap.put( + IPTC.JOB_ID, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_TRANSMISSION_REFERENCE, + ExifToolMetadata.IPTC_ORIGINAL_TRANSMISSION_REFERENCE )); + _tikaToExiftoolMetadataMap.put( + IPTC.TITLE, + Arrays.asList( + ExifToolMetadata.XMP_DC_TITLE, + ExifToolMetadata.IPTC_OBJECT_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.COPYRIGHT_NOTICE, + Arrays.asList( + ExifToolMetadata.XMP_DC_RIGHTS, + ExifToolMetadata.IPTC_COPYRIGHT_NOTICE )); + _tikaToExiftoolMetadataMap.put( + IPTC.CREATOR, + Arrays.asList( + ExifToolMetadata.XMP_DC_CREATOR, + ExifToolMetadata.IPTC_BY_LINE )); + _tikaToExiftoolMetadataMap.put( + IPTC.CREATORS_JOB_TITLE, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_AUTHORS_POSITION, + ExifToolMetadata.IPTC_BY_LINE_TITLE )); + _tikaToExiftoolMetadataMap.put( + IPTC.CREDIT_LINE, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_CREDIT, + ExifToolMetadata.IPTC_CREDIT )); + _tikaToExiftoolMetadataMap.put( + IPTC.RIGHTS_USAGE_TERMS, + Arrays.asList( + ExifToolMetadata.XMP_XMPRIGHTS_USAGE_TERMS )); + _tikaToExiftoolMetadataMap.put( + IPTC.SOURCE, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_SOURCE, + ExifToolMetadata.IPTC_SOURCE )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_ADDRESS, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_ADDRESS )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_CITY, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_CITY )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_COUNTRY, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_COUNTRY )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_EMAIL, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_EMAIL_WORK )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_PHONE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_TELEPHONE_WORK )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_POSTAL_CODE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_POSTAL_CODE )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_STATE_PROVINCE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_REGION )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTACT_INFO_WEB_URL, + Arrays.asList( + ExifToolMetadata.XMP_IPTCCORE_CREATOR_CONTACT_INFO_URL_WORK )); + + _tikaToExiftoolMetadataMap.put( + IPTC.URGENCY, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_URGENCY, + ExifToolMetadata.IPTC_URGENCY )); + _tikaToExiftoolMetadataMap.put( + IPTC.CATEGORY, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_CATEGORY, + ExifToolMetadata.IPTC_CATEGORY )); + _tikaToExiftoolMetadataMap.put( + IPTC.SUPPLEMENTAL_CATEGORIES, + Arrays.asList( + ExifToolMetadata.XMP_PHOTOSHOP_SUPPLEMENTAL_CATEGORIES, + ExifToolMetadata.IPTC_SUPPLEMENTAL_CATEGORIES )); + + _tikaToExiftoolMetadataMap.put( + IPTC.ADDITIONAL_MODEL_INFO, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ADDITIONAL_MODEL_INFORMATION )); + _tikaToExiftoolMetadataMap.put( + IPTC.ORGANISATION_CODE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ORGANISATION_IN_IMAGE_CODE )); + _tikaToExiftoolMetadataMap.put( + IPTC.CONTROLLED_VOCABULARY_TERM, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_CONTROLLED_VOCABULAR_TERM )); + _tikaToExiftoolMetadataMap.put( + IPTC.MODEL_AGE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_MODEL_AGE )); + _tikaToExiftoolMetadataMap.put( + IPTC.ORGANISATION_NAME, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ORGANISATION_IN_IMAGE_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.PERSON, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_PERSON_IN_IMAGE )); + _tikaToExiftoolMetadataMap.put( + IPTC.DIGITAL_IMAGE_GUID, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_DIGITAL_IMAGE_GUID )); + _tikaToExiftoolMetadataMap.put( + IPTC.DIGITAL_SOURCE_TYPE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_DIGITAL_SOURCE_TYPE )); + _tikaToExiftoolMetadataMap.put( + IPTC.EVENT, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_EVENT )); + _tikaToExiftoolMetadataMap.put( + IPTC.IMAGE_SUPPLIER_ID, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_IMAGE_SUPPLIER_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.IMAGE_SUPPLIER_NAME, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_IMAGE_SUPPLIER_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.IMAGE_SUPPLIER_IMAGE_ID, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_IMAGE_SUPPLIER_IMAGE_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.IPTC_LAST_EDITED, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_IPTC_LAST_EDITED )); + _tikaToExiftoolMetadataMap.put( + IPTC.MAX_AVAIL_HEIGHT, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_MAX_AVAIL_HEIGHT )); + _tikaToExiftoolMetadataMap.put( + IPTC.MAX_AVAIL_WIDTH, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_MAX_AVAIL_WIDTH )); + _tikaToExiftoolMetadataMap.put( + IPTC.PLUS_VERSION, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_VERSION )); + _tikaToExiftoolMetadataMap.put( + IPTC.COPYRIGHT_OWNER_ID, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_COPYRIGHT_OWNER_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.COPYRIGHT_OWNER_NAME, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_COPYRIGHT_OWNER_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.IMAGE_CREATOR_ID, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_IMAGE_CREATOR_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.IMAGE_CREATOR_NAME, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_IMAGE_CREATOR_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_ID, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_NAME, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_CITY, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_CITY )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_COUNTRY, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_COUNTRY )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_EMAIL, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_EMAIL )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_EXTENDED_ADDRESS, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_EXTENDED_ADDRESS )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_POSTAL_CODE, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_POSTAL_CODE )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_REGION, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_REGION )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_STREET_ADDRESS, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_STREET_ADDRESS )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_TELEPHONE_1, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_TELEPHONE_1 )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_TELEPHONE_2, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_TELEPHONE_2 )); + _tikaToExiftoolMetadataMap.put( + IPTC.LICENSOR_URL, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_LICENSOR_URL )); + _tikaToExiftoolMetadataMap.put( + IPTC.MINOR_MODEL_AGE_DISCLOSURE, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_MINOR_MODEL_AGE_DISCLOSURE )); + _tikaToExiftoolMetadataMap.put( + IPTC.MODEL_RELEASE_ID, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_MODEL_RELEASE_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.MODEL_RELEASE_STATUS, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_MODEL_RELEASE_STATUS )); + _tikaToExiftoolMetadataMap.put( + IPTC.PROPERTY_RELEASE_ID, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_PROPERTY_RELEASE_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.PROPERTY_RELEASE_STATUS, + Arrays.asList( + ExifToolMetadata.XMP_PLUS_PROPERTY_RELEASE_STATUS )); + _tikaToExiftoolMetadataMap.put( + IPTC.ARTWORK_OR_OBJECT_DETAIL_COPYRIGHT_NOTICE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ARTWORK_OR_OBJECT_COPYRIGHT_NOTICE )); + _tikaToExiftoolMetadataMap.put( + IPTC.ARTWORK_OR_OBJECT_DETAIL_CREATOR, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ARTWORK_OR_OBJECT_CREATOR )); + _tikaToExiftoolMetadataMap.put( + IPTC.ARTWORK_OR_OBJECT_DETAIL_DATE_CREATED, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ARTWORK_OR_OBJECT_DATE_CREATED )); + _tikaToExiftoolMetadataMap.put( + IPTC.ARTWORK_OR_OBJECT_DETAIL_SOURCE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ARTWORK_OR_OBJECT_SOURCE )); + _tikaToExiftoolMetadataMap.put( + IPTC.ARTWORK_OR_OBJECT_DETAIL_SOURCE_INVENTORY_NUMBER, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ARTWORK_OR_OBJECT_SOURCE_INVENTORY_NO )); + _tikaToExiftoolMetadataMap.put( + IPTC.ARTWORK_OR_OBJECT_DETAIL_TITLE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_ARTWORK_OR_OBJECT_TITLE )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_CREATED_CITY, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_CREATED_CITY )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_CREATED_COUNTRY_CODE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_CREATED_COUNTRY_CODE )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_CREATED_COUNTRY_NAME, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_CREATED_COUNTRY_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_CREATED_PROVINCE_OR_STATE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_CREATED_PROVINCE_STATE )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_CREATED_SUBLOCATION, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_CREATED_SUBLOCATION )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_CREATED_WORLD_REGION, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_CREATED_WORLD_REGION )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_SHOWN_CITY, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_SHOWN_CITY )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_SHOWN_COUNTRY_CODE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_SHOWN_COUNTRY_CODE )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_SHOWN_COUNTRY_NAME, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_SHOWN_COUNTRY_NAME )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_SHOWN_PROVINCE_OR_STATE, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_SHOWN_PROVINCE_STATE )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_SHOWN_SUBLOCATION, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_SHOWN_SUBLOCATION )); + _tikaToExiftoolMetadataMap.put( + IPTC.LOCATION_SHOWN_WORLD_REGION, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_LOCATION_SHOWN_WORLD_REGION )); + _tikaToExiftoolMetadataMap.put( + IPTC.REGISTRY_ENTRY_CREATED_ITEM_ID, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_REGISTRY_ITEM_ID )); + _tikaToExiftoolMetadataMap.put( + IPTC.REGISTRY_ENTRY_CREATED_ORGANISATION_ID, + Arrays.asList( + ExifToolMetadata.XMP_IPTCEXT_REGISTRY_ORGANISATION_ID )); + + + + _tikaToExiftoolMetadataMap.put( + TikaCoreProperties.TITLE, + Arrays.asList( + ExifToolMetadata.XMP_DC_TITLE, + ExifToolMetadata.IPTC_OBJECT_NAME )); + _tikaToExiftoolMetadataMap.put( + TikaCoreProperties.DESCRIPTION, + Arrays.asList( + ExifToolMetadata.XMP_DC_DESCRIPTION, + ExifToolMetadata.IPTC_CAPTION )); + _tikaToExiftoolMetadataMap.put( + TikaCoreProperties.CREATOR, + Arrays.asList( + ExifToolMetadata.XMP_DC_CREATOR, + ExifToolMetadata.IPTC_BY_LINE )); + _tikaToExiftoolMetadataMap.put( + TikaCoreProperties.KEYWORDS, + Arrays.asList( + ExifToolMetadata.XMP_DC_SUBJECT, + ExifToolMetadata.IPTC_KEYWORDS )); + + // Create reverse mapping + for (Property tikaMetadata : _tikaToExiftoolMetadataMap.keySet()) { + for (Property exiftoolMetadata : _tikaToExiftoolMetadataMap.get(tikaMetadata)) { + List mapping = _exiftoolToTikaMetadataMap.get(exiftoolMetadata); + if (mapping == null) { + mapping = new ArrayList(); + } + mapping.add(tikaMetadata); + _exiftoolToTikaMetadataMap.put(exiftoolMetadata, mapping); + } + } + + } + + /** + * Gets a map of Tika metadata names to an array of ExifTool metadata names. Most + * useful for constructing command line arguments. + * + * Multiple ExifTool metadata names are provided since it is commonplace to write the + * same general, Tika metadata value to several metadata fields. For example, + * a copyright notice Tika field might be written to EXIF, legacy IPTC, and + * XMP. + * + * @return the map of Tika metadata names to ExifTool names + */ + public Map> getTikaToExiftoolMetadataMap() { + return _tikaToExiftoolMetadataMap; + } + + /** + * Gets a map of ExifTool metadata names to a single Tika metadata name. Most + * useful for parsers. + * + * @return the map of ExifTool metadata names to Tika names + */ + public Map> getExiftoolToTikaMetadataMap() { + return _exiftoolToTikaMetadataMap; + } + +} diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java new file mode 100644 index 00000000000..7e7a9e6dd11 --- /dev/null +++ b/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import java.util.List; +import java.util.Map; + +import org.apache.tika.metadata.Property; + +/** + * Defines method to convert to and from Tika metadata properties + * and ExifTool metadata fields. + * + * @author rgauss + */ +public interface ExiftoolTikaMapper { + + /** + * Gets a map of Tika metadata names to an array of ExifTool metadata names. Most + * useful for constructing command line arguments. + * + * Multiple ExifTool metadata names are provided since it is commonplace to write the + * same general, Tika metadata value to several metadata fields. For example, + * a copyright notice Tika field might be written to EXIF, legacy IPTC, and + * XMP. + * + * @return the map of Tika metadata names to ExifTool names + */ + public Map> getTikaToExiftoolMetadataMap(); + + /** + * Gets a map of ExifTool metadata names to a single Tika metadata name. Most + * useful for parsers. + * + * @return the map of ExifTool metadata names to Tika names + */ + public Map> getExiftoolToTikaMetadataMap(); + +} diff --git a/tika-exiftool/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties b/tika-exiftool/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties new file mode 100644 index 00000000000..33ee359e2c4 --- /dev/null +++ b/tika-exiftool/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties @@ -0,0 +1 @@ +exiftool.executable=exiftool diff --git a/tika-exiftool/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java b/tika-exiftool/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java new file mode 100644 index 00000000000..65ec6117b49 --- /dev/null +++ b/tika-exiftool/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.embedder.exiftool; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Date; + +import org.apache.tika.embedder.Embedder; +import org.apache.tika.embedder.ExternalEmbedderTest; +import org.apache.tika.embedder.exiftool.ExiftoolExternalEmbedder; +import org.apache.tika.metadata.IPTC; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.Parser; +import org.apache.tika.parser.exiftool.ExiftoolImageParser; +import org.apache.tika.parser.exiftool.ExiftoolTikaIptcMapper; + +/** + * Unit test for the ExiftoolExternalEmbedder + * + * @author rgauss + * + */ +public class ExiftoolExternalEmbedderTest extends ExternalEmbedderTest { + + private static final String TEST_IMAGE_PATH = "/test-documents/testJPEG_IPTC_EXT.jpg"; + + @Override + protected org.apache.tika.metadata.Metadata getMetadataToEmbed(Date timestamp) { + Metadata metadataToEmbed = new Metadata(); + + metadataToEmbed.add(IPTC.COPYRIGHT_NOTICE.getName(), + getExpectedMetadataValueString(IPTC.COPYRIGHT_NOTICE.getName(), timestamp)); + metadataToEmbed.add(IPTC.DESCRIPTION.getName(), + getExpectedMetadataValueString(IPTC.DESCRIPTION.getName(), timestamp)); + metadataToEmbed.add(IPTC.CREDIT_LINE.getName(), + getExpectedMetadataValueString(IPTC.CREDIT_LINE.getName(), timestamp)); + metadataToEmbed.add(IPTC.KEYWORDS.getName(), + this.getClass().getSimpleName() + "_keyword1"); + metadataToEmbed.add(IPTC.KEYWORDS.getName(), + this.getClass().getSimpleName() + "_keyword2"); + metadataToEmbed.add(IPTC.CONTACT_INFO_EMAIL.getName(), + getExpectedMetadataValueString(IPTC.CONTACT_INFO_EMAIL.getName(), timestamp)); + metadataToEmbed.add(IPTC.ARTWORK_OR_OBJECT_DETAIL_TITLE.getName(), + getExpectedMetadataValueString(IPTC.ARTWORK_OR_OBJECT_DETAIL_TITLE.getName(), timestamp)); + + return metadataToEmbed; + } + + @Override + protected Embedder getEmbedder() { + return new ExiftoolExternalEmbedder(new ExiftoolTikaIptcMapper()); + } + + @Override + protected InputStream getSourceStandardInputStream() { + return this.getClass().getResourceAsStream(TEST_IMAGE_PATH); + } + + @Override + protected File getSourceInputFile() throws FileNotFoundException { + URL origUrl = this.getClass().getResource(TEST_IMAGE_PATH); + if (origUrl == null) { + throw new FileNotFoundException("could not load " + TEST_IMAGE_PATH); + } + try { + return new File(origUrl.toURI()); + } catch (URISyntaxException e) { + throw new FileNotFoundException(e.getMessage()); + } + } + + @Override + protected Parser getParser() { + return new ExiftoolImageParser(); + } + + @Override + protected boolean getIsMetadataExpectedInOutput() { + return false; + } + +} diff --git a/tika-exiftool/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java b/tika-exiftool/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java new file mode 100644 index 00000000000..37cefa974a4 --- /dev/null +++ b/tika-exiftool/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.parser.exiftool; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.TimeZone; + +import junit.framework.TestCase; + +import org.apache.tika.metadata.IPTC; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.Property; +import org.apache.tika.metadata.TIFF; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.parser.Parser; +import org.xml.sax.helpers.DefaultHandler; + +public class ExifToolImageParserTest extends TestCase { + /** + * Logger for this class + */ + private static final Log logger = LogFactory + .getLog(ExifToolImageParserTest.class); + + private final Parser parser = new ExiftoolImageParser(); + + public void testJPEGIPTC() throws Exception { + Metadata metadata = new Metadata(); + metadata.set(Metadata.CONTENT_TYPE, "image/jpeg"); + InputStream stream = + getClass().getResourceAsStream("/test-documents/testJPEG_IPTC_EXT.jpg"); + parser.parse(stream, new DefaultHandler(), metadata, new ParseContext()); + + assertEquals("Washington", metadata.get(IPTC.CITY)); + assertEquals("United States", metadata.get(IPTC.COUNTRY)); + assertEquals("US", metadata.get(IPTC.COUNTRY_CODE)); + + assertEquals("A stream bank in Rock Creek Park Washington DC during a photo bike tour with ASPP DC/South chapter.", metadata.get(IPTC.DESCRIPTION)); + assertEquals("A stream bank in Rock Creek Park Washington DC during a photo bike tour with ASPP DC/South chapter.", metadata.get(Metadata.DESCRIPTION)); + + assertEquals("Rock Creek Park", metadata.get(IPTC.HEADLINE)); + assertEquals("Downstream", metadata.get(Metadata.TITLE)); + + assertEquals("intellectual genre", metadata.get(IPTC.INTELLECTUAL_GENRE)); + + List iptcKeywords = Arrays.asList(metadata.getValues(IPTC.KEYWORDS)); + assertTrue(iptcKeywords.contains("stream")); + assertTrue(iptcKeywords.contains("park")); + assertTrue(iptcKeywords.contains("bank")); + assertEquals(5, iptcKeywords.size()); + List tikaKeywords = Arrays.asList(metadata.getValues(Metadata.KEYWORDS)); + assertTrue(Arrays.toString(tikaKeywords.toArray()).contains("stream")); + assertTrue(Arrays.toString(tikaKeywords.toArray()).contains("park")); + assertTrue(Arrays.toString(tikaKeywords.toArray()).contains("bank")); + assertEquals(5, tikaKeywords.size()); + + + assertEquals("DC", metadata.get(IPTC.PROVINCE_OR_STATE)); + + List iptcSceneCode = Arrays.asList(metadata.getValues(IPTC.SCENE_CODE)); + assertEquals(2, iptcSceneCode.size()); + assertTrue(Arrays.toString(iptcSceneCode.toArray()).contains("iptc scene 1")); + assertTrue(Arrays.toString(iptcSceneCode.toArray()).contains("iptc scene 2")); + + List iptcSubjectCode = Arrays.asList(metadata.getValues(IPTC.SUBJECT_CODE)); + assertEquals(2, iptcSubjectCode.size()); + assertTrue(Arrays.toString(iptcSubjectCode.toArray()).contains("iptc subject code 1")); + assertTrue(Arrays.toString(iptcSubjectCode.toArray()).contains("iptc subject code 2")); + + assertEquals("Rock Creek Park", metadata.get(IPTC.SUBLOCATION)); + + GregorianCalendar calendar = new GregorianCalendar(); + calendar.set(Calendar.YEAR, 2011); + calendar.set(Calendar.MONTH, 7); + calendar.set(Calendar.DATE, 31); + calendar.set(Calendar.HOUR_OF_DAY, 12); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + assertEquals(calendar.getTime(), metadata.getDate(IPTC.DATE_CREATED)); + + assertEquals("Ray Gauss II", metadata.get(IPTC.DESCRIPTION_WRITER)); + assertEquals("instructions", metadata.get(IPTC.INSTRUCTIONS)); + assertEquals("job identifier", metadata.get(IPTC.JOB_ID)); + assertEquals("Downstream", metadata.get(IPTC.TITLE)); + assertTrue(metadata.get(IPTC.COPYRIGHT_NOTICE).contains("Ray Gauss II")); + + List creators = Arrays.asList(metadata.getValues(IPTC.CREATOR)); + assertTrue(Arrays.toString(creators.toArray()).contains("Ray Gauss II")); + + assertEquals("DAM Architect", metadata.get(IPTC.CREATORS_JOB_TITLE)); + assertEquals("provider", metadata.get(IPTC.CREDIT_LINE)); + assertEquals("rights usage terms", metadata.get(IPTC.RIGHTS_USAGE_TERMS)); + assertEquals("source", metadata.get(IPTC.SOURCE)); + assertEquals("1234 Some Road", metadata.get(IPTC.CONTACT_INFO_ADDRESS)); + assertEquals("Atlanta", metadata.get(IPTC.CONTACT_INFO_CITY)); + assertEquals("US", metadata.get(IPTC.CONTACT_INFO_COUNTRY)); + + List ciWorkEmails = Arrays.asList(metadata.getValues(IPTC.CONTACT_INFO_EMAIL)); + // Photoshop does not support true multi-value here + assertTrue(Arrays.toString(ciWorkEmails.toArray()).contains("info@alfresco.com")); + assertTrue(Arrays.toString(ciWorkEmails.toArray()).contains("other@example.com")); + + List ciWorkTels = Arrays.asList(metadata.getValues(IPTC.CONTACT_INFO_PHONE)); + // Photoshop does not support true multi-value here + assertTrue(Arrays.toString(ciWorkTels.toArray()).contains("555-1234")); + assertTrue(Arrays.toString(ciWorkTels.toArray()).contains("555-4321")); + + assertEquals("30339", metadata.get(IPTC.CONTACT_INFO_POSTAL_CODE)); + assertEquals("GA", metadata.get(IPTC.CONTACT_INFO_STATE_PROVINCE)); + + List ciWorkUrls = Arrays.asList(metadata.getValues(IPTC.CONTACT_INFO_WEB_URL)); + // Photoshop does not support true multi-value here + assertTrue(Arrays.toString(ciWorkUrls.toArray()).contains("http://alfresco.com")); + assertTrue(Arrays.toString(ciWorkUrls.toArray()).contains("http://example.com")); + + assertEquals("rocky 1 and rocky 2 are big", metadata.get(IPTC.ADDITIONAL_MODEL_INFO)); + + List orgCodes = Arrays.asList(metadata.getValues(IPTC.ORGANISATION_CODE)); + assertEquals(2, orgCodes.size()); + assertEquals("ASPP", orgCodes.get(0)); + assertEquals("OTHER_ORG", orgCodes.get(1)); + + // List cvTerms = Arrays.asList(metadata.getValues(IPTC.CONTROLLED_VOCABULARY_TERM)); + + List modelAges = Arrays.asList(metadata.getValues(IPTC.MODEL_AGE)); + assertEquals(2, modelAges.size()); + assertEquals("1000", modelAges.get(0)); + assertEquals("1001", modelAges.get(1)); + + List orgNames = Arrays.asList(metadata.getValues(IPTC.ORGANISATION_NAME)); + assertEquals(2, orgNames.size()); + assertEquals("ASPP", orgNames.get(0)); + assertEquals("Other Org", orgNames.get(1)); + + List peopleShown = Arrays.asList(metadata.getValues(IPTC.PERSON)); + assertEquals(2, peopleShown.size()); + assertEquals("rocky 1", peopleShown.get(0)); + assertEquals("rocky 2", peopleShown.get(1)); + + assertEquals("http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture", metadata.get(IPTC.DIGITAL_SOURCE_TYPE)); + assertEquals("Photo Bike Tour", metadata.get(IPTC.EVENT)); + + assertEquals("RGAUSS", metadata.get(IPTC.IMAGE_SUPPLIER_ID)); + assertEquals("Ray Gauss II", metadata.get(IPTC.IMAGE_SUPPLIER_NAME)); + assertEquals("supplier image ID", metadata.get(IPTC.IMAGE_SUPPLIER_IMAGE_ID)); + assertEquals("3456", metadata.get(IPTC.MAX_AVAIL_HEIGHT)); + assertEquals("5184", metadata.get(IPTC.MAX_AVAIL_WIDTH)); + assertEquals("1.2.0", metadata.get(IPTC.PLUS_VERSION)); + + List copyrightOwnerIds = Arrays.asList(metadata.getValues(IPTC.COPYRIGHT_OWNER_ID)); + assertEquals(1, copyrightOwnerIds.size()); + assertEquals("RGAUSS", copyrightOwnerIds.get(0)); + // assertEquals("", copyrightOwnerIds.get(1)); // TODO: Get ExifTool to preserve empty values + + List copyrightOwnerNames = Arrays.asList(metadata.getValues(IPTC.COPYRIGHT_OWNER_NAME)); + assertEquals(2, copyrightOwnerNames.size()); + assertEquals("Ray Gauss II", copyrightOwnerNames.get(0)); + assertEquals("GG", copyrightOwnerNames.get(1)); + + List imageCreatorIds = Arrays.asList(metadata.getValues(IPTC.IMAGE_CREATOR_ID)); + assertEquals(1, imageCreatorIds.size()); + assertEquals("RGAUSS", imageCreatorIds.get(0)); + // assertEquals("", imageCreatorIds.get(1)); // TODO: Get ExifTool to preserve empty values + + assertTrue(metadata.isMultiValued(IPTC.IMAGE_CREATOR_NAME)); + List imageCreatorNames = Arrays.asList(metadata.getValues(IPTC.IMAGE_CREATOR_NAME)); + assertEquals(2, imageCreatorNames.size()); + assertEquals("Ray Gauss II", imageCreatorNames.get(0)); + assertEquals("GG", imageCreatorNames.get(1)); + + List licensorIds = Arrays.asList(metadata.getValues(IPTC.LICENSOR_ID)); + assertEquals("RGAUSS", licensorIds.get(0)); + + assertTrue(metadata.isMultiValued(IPTC.LICENSOR_NAME)); + List licensorNames = Arrays.asList(metadata.getValues(IPTC.LICENSOR_NAME)); + assertEquals(2, licensorNames.size()); + assertEquals("Ray Gauss II", licensorNames.get(0)); + assertEquals("GG", licensorNames.get(1)); + + // Photoshop does not support licensor addresses, cities, or countries + + List licensorEmails = Arrays.asList(metadata.getValues(IPTC.LICENSOR_EMAIL)); + assertEquals("r@example.com", licensorEmails.get(0)); + // assertEquals("", licensorEmails.get(1)); // TODO: Get ExifTool to preserve empty values + List licensorTel1 = Arrays.asList(metadata.getValues(IPTC.LICENSOR_TELEPHONE_1)); + assertEquals("555-5555", licensorTel1.get(0)); + // assertEquals("", licensorTel1.get(1)); // TODO: Get ExifTool to preserve empty values + List licensorTel2 = Arrays.asList(metadata.getValues(IPTC.LICENSOR_TELEPHONE_2)); + assertEquals("555-4444", licensorTel2.get(0)); + // assertEquals("", licensorTel2.get(1)); // TODO: Get ExifTool to preserve empty values + List licensorUrls = Arrays.asList(metadata.getValues(IPTC.LICENSOR_URL)); + assertEquals("http://rgauss.com", licensorUrls.get(0)); + // assertEquals("", licensorUrls.get(1)); // TODO: Get ExifTool to preserve empty values + + assertEquals("Age Unknown", metadata.get(IPTC.MINOR_MODEL_AGE_DISCLOSURE)); + List modelReleaseIds = Arrays.asList(metadata.getValues(IPTC.MODEL_RELEASE_ID)); + assertEquals("model release id 1", modelReleaseIds.get(0)); + assertEquals("model release id 2", modelReleaseIds.get(1)); + assertEquals("Not Applicable", metadata.get(IPTC.MODEL_RELEASE_STATUS)); + + List propertyReleaseIds = Arrays.asList(metadata.getValues(IPTC.PROPERTY_RELEASE_ID)); + assertEquals("prop release id 1", propertyReleaseIds.get(0)); + assertEquals("prop release id 2", propertyReleaseIds.get(1)); + assertEquals("Not Applicable", metadata.get(IPTC.PROPERTY_RELEASE_STATUS)); + + List aoCopyright = Arrays.asList(metadata.getValues(IPTC.ARTWORK_OR_OBJECT_DETAIL_COPYRIGHT_NOTICE)); + assertEquals("Ray Gauss II", aoCopyright.get(0)); + // assertEquals("", aoCopyright.get(1)); // TODO: Get ExifTool to preserve empty values + // assertEquals("", aoCopyright.get(2)); // TODO: Get ExifTool to preserve empty values + List aoCreator = Arrays.asList(metadata.getValues(IPTC.ARTWORK_OR_OBJECT_DETAIL_CREATOR)); + assertEquals("Mother Nature", aoCreator.get(0)); + assertEquals("Man", aoCreator.get(1)); + assertEquals("Mother Nature", aoCreator.get(2)); + List aoDateCreated = Arrays.asList(metadata.getValues(IPTC.ARTWORK_OR_OBJECT_DETAIL_DATE_CREATED)); + assertEquals("1890:01:01", aoDateCreated.get(0)); + // assertEquals("", aoDateCreated.get(1)); // TODO: Get ExifTool to preserve empty values + assertEquals("1901:02:01", aoDateCreated.get(1)); + // assertEquals("", aoDateCreated.get(2)); // TODO: Get ExifTool to preserve empty values + List aoSource = Arrays.asList(metadata.getValues(IPTC.ARTWORK_OR_OBJECT_DETAIL_SOURCE)); + assertEquals("National Park Service", aoSource.get(0)); + // assertEquals("", aoSource.get(1)); // TODO: Get ExifTool to preserve empty values + // assertEquals("", aoSource.get(2)); // TODO: Get ExifTool to preserve empty values + List aoSourceInventoryNum = Arrays.asList(metadata.getValues(IPTC.ARTWORK_OR_OBJECT_DETAIL_SOURCE_INVENTORY_NUMBER)); + assertEquals("123456", aoSourceInventoryNum.get(0)); + // assertEquals("", aoSourceInventoryNum.get(1)); // TODO: Get ExifTool to preserve empty values + assertEquals("654321", aoSourceInventoryNum.get(1)); // This should be index 2, TODO: Get ExifTool to preserve empty values + List aoSourceTitles = Arrays.asList(metadata.getValues(IPTC.ARTWORK_OR_OBJECT_DETAIL_TITLE)); + assertEquals("Rock Creek Stream Bank", aoSourceTitles.get(0)); + assertEquals("Pollution", aoSourceTitles.get(1)); + assertEquals("Some Tree", aoSourceTitles.get(2)); + + List locationShownCity = Arrays.asList(metadata.getValues(IPTC.LOCATION_SHOWN_CITY)); + assertEquals("Washington", locationShownCity.get(0)); + // assertEquals("", locationShownCity.get(1)); // TODO: Get ExifTool to preserve empty values + List locationShownCountryCode = Arrays.asList(metadata.getValues(IPTC.LOCATION_SHOWN_COUNTRY_CODE)); + assertEquals("US", locationShownCountryCode.get(0)); + // assertEquals("", locationShownCountryCode.get(1)); // TODO: Get ExifTool to preserve empty values + List locationShownCountryName = Arrays.asList(metadata.getValues(IPTC.LOCATION_SHOWN_COUNTRY_NAME)); + assertEquals("United States", locationShownCountryName.get(0)); + // assertEquals("", locationShownCountryName.get(1)); // TODO: Get ExifTool to preserve empty values + List locationShownState = Arrays.asList(metadata.getValues(IPTC.LOCATION_SHOWN_PROVINCE_OR_STATE)); + assertEquals("D.C.", locationShownState.get(0)); + // assertEquals("", locationShownState.get(1)); // TODO: Get ExifTool to preserve empty values + List locationShownSublocation = Arrays.asList(metadata.getValues(IPTC.LOCATION_SHOWN_SUBLOCATION)); + assertEquals("Rock Creek Park Sub", locationShownSublocation.get(0)); + assertEquals("Stream Section", locationShownSublocation.get(1)); + List locationShownWorldRegion = Arrays.asList(metadata.getValues(IPTC.LOCATION_SHOWN_WORLD_REGION)); + assertEquals("North America", locationShownWorldRegion.get(0)); + // assertEquals("", locationShownWorldRegion.get(1)); // TODO: Get ExifTool to preserve empty values + + assertEquals("Washington", metadata.get(IPTC.LOCATION_CREATED_CITY)); + assertEquals("US", metadata.get(IPTC.LOCATION_CREATED_COUNTRY_CODE)); + assertEquals("United States", metadata.get(IPTC.LOCATION_CREATED_COUNTRY_NAME)); + assertEquals("D.C.", metadata.get(IPTC.LOCATION_CREATED_PROVINCE_OR_STATE)); + assertEquals("Rock Creek Park", metadata.get(IPTC.LOCATION_CREATED_SUBLOCATION)); + assertEquals("North America", metadata.get(IPTC.LOCATION_CREATED_WORLD_REGION)); + + assertTrue(IPTC.REGISTRY_ENTRY_CREATED_ORGANISATION_ID.isMultiValuePermitted()); + assertTrue(metadata.isMultiValued(IPTC.REGISTRY_ENTRY_CREATED_ORGANISATION_ID)); + List registryEntryOrgIds = Arrays.asList(metadata.getValues(IPTC.REGISTRY_ENTRY_CREATED_ORGANISATION_ID)); + assertEquals(2, registryEntryOrgIds.size()); + assertEquals("PLUS", registryEntryOrgIds.get(0)); + // assertEquals("", registryEntryOrgIds.get(1)); // TODO: Get ExifTool to preserve empty values + assertEquals("ORG 2", registryEntryOrgIds.get(1)); // This should be index 2, TODO: Get ExifTool to preserve empty values + + assertTrue(IPTC.REGISTRY_ENTRY_CREATED_ORGANISATION_ID.isMultiValuePermitted()); + assertTrue(metadata.isMultiValued(IPTC.REGISTRY_ENTRY_CREATED_ITEM_ID)); + List registryEntryItemIds = Arrays.asList(metadata.getValues(IPTC.REGISTRY_ENTRY_CREATED_ITEM_ID)); + assertEquals(registryEntryItemIds.size(), 3); + assertEquals("100-ABC-ABC-555", registryEntryItemIds.get(0)); + assertEquals("11223344", registryEntryItemIds.get(1)); + assertEquals("55667788", registryEntryItemIds.get(2)); + + } + + public void testJPEGCustomXmp() throws Exception { + Metadata metadata = new Metadata(); + metadata.set(Metadata.CONTENT_TYPE, "image/jpeg"); + InputStream stream = + getClass().getResourceAsStream("/test-documents/testJPEG_IPTC_EXT.jpg"); + ArrayList passthroughXmpProperties = new ArrayList(2); + passthroughXmpProperties.add(Property.internalText("XMP-custom:Text")); + passthroughXmpProperties.add(Property.internalText("XMP-custom:TextML")); + Parser passthroughParser = new ExiftoolImageParser(null, passthroughXmpProperties); + passthroughParser.parse(stream, new DefaultHandler(), metadata, new ParseContext()); + + assertEquals("customTextField", metadata.get("XMP-custom:Text")); + assertEquals("customMultilineField", metadata.get("XMP-custom:TextML")); + } + + public void testJPEG() throws Exception { + Metadata metadata = new Metadata(); + metadata.set(Metadata.CONTENT_TYPE, "image/jpeg"); + InputStream stream = + getClass().getResourceAsStream("/test-documents/testJPEG.jpg"); + parser.parse(stream, new DefaultHandler(), metadata, new ParseContext()); + + assertEquals("100", metadata.get(TIFF.IMAGE_WIDTH)); + for (String name : metadata.names()) { + logger.trace("JPEG-- " + name + "=" + metadata.get(name)); + } + } + + public void testPNGIPTC() throws Exception { + Metadata metadata = new Metadata(); + metadata.set(Metadata.CONTENT_TYPE, "image/png"); + InputStream stream = + getClass().getResourceAsStream("/test-documents/testPNG_IPTC.png"); + parser.parse(stream, new DefaultHandler(), metadata, new ParseContext()); + + for (String name : metadata.names()) { + logger.trace("PNG-- " + name + "=" + metadata.get(name)); + } + assertEquals("100", metadata.get(TIFF.IMAGE_WIDTH)); + assertEquals("Cat in a garden", metadata.get(IPTC.HEADLINE)); + } + + public void testTIFFIPTC() throws Exception { + Metadata metadata = new Metadata(); + metadata.set(Metadata.CONTENT_TYPE, "image/tiff"); + InputStream stream = + getClass().getResourceAsStream("/test-documents/testTIFF_IPTC.tif"); + parser.parse(stream, new DefaultHandler(), metadata, new ParseContext()); + + for (String name : metadata.names()) { + logger.trace("TIFF-- " + name + "=" + metadata.get(name)); + } + List iptcKeywords = Arrays.asList(metadata.getValues(IPTC.KEYWORDS)); + assertTrue(iptcKeywords.contains("garden")); + assertTrue(iptcKeywords.contains("cat")); + assertEquals("Cat in a garden", metadata.get(IPTC.HEADLINE)); + assertEquals("100", metadata.get(TIFF.IMAGE_WIDTH)); + assertEquals("75", metadata.get(TIFF.IMAGE_LENGTH)); + assertEquals("Inch", metadata.get(TIFF.RESOLUTION_UNIT)); + assertEquals("1", metadata.get(TIFF.ORIENTATION)); + assertEquals("8", metadata.get(TIFF.BITS_PER_SAMPLE)); + } + +} diff --git a/tika-exiftool/src/test/resources/log4j.properties b/tika-exiftool/src/test/resources/log4j.properties new file mode 100644 index 00000000000..3f8968f2cd1 --- /dev/null +++ b/tika-exiftool/src/test/resources/log4j.properties @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#info,debug, error,fatal ... +log4j.rootLogger=debug,stdout + +#console +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n diff --git a/tika-exiftool/src/test/resources/test-documents/testJPEG.jpg b/tika-exiftool/src/test/resources/test-documents/testJPEG.jpg new file mode 100644 index 0000000000000000000000000000000000000000..09d45d460b76ae3172d9afd0009a3991ca91649a GIT binary patch literal 7686 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 Y2z1rZcf;NjN32c@=%*TyrCrVa58cx?$^ZZW literal 0 HcmV?d00001 diff --git a/tika-exiftool/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg b/tika-exiftool/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg new file mode 100644 index 0000000000000000000000000000000000000000..24598a0dd2466324dfb7960bc65cd611c38c7b3a GIT binary patch literal 27421 zcmeHw2Ut_jv*<}7p%bczh!H`Qn$SV%T|kg7q8O3@ku*~%Dk5O-qGE4|qSzZ2R1^y~ zR761q1T5GED+&T{&q*O5`upAgefNIf`<@4L&Ya!Z+1c6IHamOldDzoT80C}BNQ9u^ zV8|4LAbCg~1wj}9p`aviLy2J@0HYBYj(d#4iNnz#TonR$hz5R5C^ZDeflzN00fAKk zJPxHH#s|bv+F}?`Lg_(rNL~;s1~o(qPeqNAp-)EW5CL{F${K;S0K5=227y%oyg6q* z0uundcL4*!ifNvOAnaUFCh+G$7DO>@a zX9;;x*c`U#PV@;0C)$&|AxQD(90)QYNS+=vP6C}63RC4LbGSsWaC_pYU<#GNh66@J z)+DlxGs)hWOeT`8ovm%1?QEbhN(Rx7BINUl0Rd3?USgkjP*ljISMHenUb$m7_mR6) z)|ld6S!0gM$~tGGqz*7=a<)j}@`Z-Ib+*t%1fxN@RzeUS(m z2NbAK)HVP`0As{BFf3^&Ff7tzB~bRUh1Lj7pr=^BLR*ASP%3Ovp&bIlIB77g0W}KJ zCxR%@-Xu?0o=_o~**XIW{V@_yumL}L@Us&? z!2_m_x&of<;Af4zqh?XTQyr31@dHmMlC1;D+S-ge`&d?A(I`osVV|O4#; zdKy3>@jaJOZh%WU6mkKE;R^$LWvm;=8sgqole2g+>zd^ zBWOP5I|cTG?nOSOe>yhzQnm2eXJ3;wh4YTDy?p=qmt7~WJb3XnDlv6_{_c{iH7(y9 z{GyZ67OX2eSyB74T^&NB0k>F$Cp=Eh4q;(9Spy3=m~N;kXPv!D3+CWrSXG;iQ9?5> zC(kQfo65KSXpDmyz>l;mzXzDuIf&*HX)mBZm9p@A4orGk=y?n&V~{-6AvfsdmdtI< zX4LpEfs4jwT|9lht2DBhm16p&`HkyH<4S9_(v&Q_vTf(W?dt+mR#7Kg;)s4NT}Dx~ zz!JM#gsJ!v+O{X`)}1|2R_NjI#9Q-Qxw_?7Q95|*$)`HYyjyyp-1gatAoja=ERPg? zW$P;Xs#E@kf`*eR=iVQiCV#%d)^=s>Q6+t~!#QbAjo00e)Es%>HoP)Jb>zO3s!N~G z-R^8gE9-3aE?9idK6q_9F)bE=`WQV}fRX&LZFLp450GoO|%)5Z%dce)aAqAw`{?tM&-( zB4YIQywRU%9v#8_$?x2KRMtK0cpP)j$I`-Lp^ZUf<ssB9m zVz?$@T=HY{S@_9|s0~GhwHlK(2~#S!wicZ;X4jVv&ABn6IyoCUe!4t0i&FNLVKV)C z>bY$_cUy*Tm`IJ)Q@HOwg!*Gj>x@LlQ$ru;?pbx{Q`XU9iesH1Hn#FW{Y%2stci_} zqgMoHB<;Vc*HO18w=gTR(Z|<)$PwLp<93(3Si7%OQmg8L96oRPvH0dI;rHgHhmR_+ z&vv`c&l=NSlWe7uviI&? zsVXa08Z_OlnSbZ>@~)O+4Xu$!RVQ{0Z@{}7%~z}JaND?PFqAyH@m0h4r3RbsA9;V^ z%8xG{k$P8~lqRxf@2cC}u(dObY@8eZ>8jl_60?b<_T=MzxAw4*DLqh6)iY8s``r7( zgAXeVp>GcXjhd0HZ|0BQmpSq{!R4so>~$w@^+4pPBKdXpSM!D#;T>7TE$3^^R&SeR ze;>2x?%-v+GY>VKc$e1YNh@GqjP+L9A!6m8SJ z?7@k5-^QdJdtLU^a29oU(IO(FcqF-dlR?-1_Gx#Uv00PSKJ-8nCM7-GGv}rk=JXJS zMP0SCrxmhaKdnVyTwcpc*Kzw&)U8xErNU`j3p3VocWK-Cnx<(7YV4K?m#Hwev0EqH zPHg0-W~$8?**V=}#L>XE4&jw~KYUg&o_y1Jzkk6tcEkC0r{pv5l*pOR?~2VJTa8%E zf|k;{FH;)Z;=dc-IJ!E@?7;aq$-53?-laco?t$=h)=d4@=Q9l!)(;6tc^or@pEhSo z!=xc6-=~~A(0#^8uQ*m=sAf%|*P@c*ef8eYh8EE>M|Q7BeBXGR)A%m@^}{abrN|L%LTVAnXY?n#&nYZ?t4(!YoKku}>jTlvN`(RV@x+Ax1ry9?9U02G!pGv7-w)&0! z<~UAh=LY&MRZ<#@C2XDjT;51M0DrMYxaPscqc;vvH^)6YQTo(j=Ij+!S=Eilx-F)@ zaC!8={;cyd_Tko<@6(yg5h>^V9Fl%ca@~0LN%3UiiKjH>`44clJrS7de=3pi$1)ccKqFRZ^euJm-W@} zA1kzv;k>llb}gzQc>I`+7-9}>8Aam;`}iW|hyDw4Vw>EY4%D4n?v08Xzt5X?kF~zN z{&5Q9XvLfuLx+I6l9RLfTU$yWUXG@0+;4Y&=cI?J`8oO?KU+%K-)6e;XPjHKWYu`T z10LF^(3ndBTzq?6D>be?Yq-Huhx;#e@aJjkB6ZZo|Bf_=J|OoYLEM zkOiziah-$iut~tJ?@^O|!%9i(&BA@BZ8EW+HF8V;Y-(2-4L38Vd z3-PP2y;2=A-zN6PI~ASdr;a;a_|WkBL@B6)ZRg&+eOqa(7w?mL@6pJ^H5=5wY<~02 zbPKjzjcUUjyV^4LIc2j)`1B3VGsZk@@~e9M(y4RO z&v`3ya~DhruDCbzxM9J2{I_#%Gq-dJpU?z)?`A(`T4v;>eQ^7-v3PlIq*dd&rZ;xb zinmUdhS>^ZYqVo5uBlp_zPQ|A$BAdv79(CeuehD|wbuQ1QFY4WPm?BM>Qe}x-0ME1 zRi~YNxGzaimqM{%e=D7sZ1S`9PE6~8&m#$g{qL=cdi7lBQ0iQ|=f*W%N~J^8Go#~# z%vBw}omo!C?zZ{ryK^@LSaFND^;B=kN?Lugx?#jK(`#Goe`Gl*RphxJQae%YwqDRW zA|f#;h5W?nMLN%alxu5zQJr9)#g}&ZwjWm8?gfP0ZQS&3>&zagC7|x5an=5;BeUA_ z#?4+(J@Mu7w)H)brQ^Ny__2j~@u{Wnw(j_Ha*5HMBJ7QdH;qS(aG9`(E*gzH_k+E|hxN6&y@EWFZ& z&;or_EQA?i?DyY(f^`)dLhGZ`A?p9aid`Q)15%bkXk>>fIfN@q|9Ig^MQ~m zU~t%ceROF5RFRO1w?{D1gGWtf2Z9~cj)bbYiOr0C5_ zgQ)>ygg)8!Xt()oGjrB4C3pU?ehNX4D`|D376!)`AqM05^%l%K&-Hyjkm` z2SZrcPJ#ejU>$)0MILN1p~4mu_!TE90*FbI+D`P*0!Xc&i9#Q(0~z);nIM`(^n|S{ z5wKMSmVrJR4-=&bgn<222m|T`e#-e#QWhAdGr=O8$qT2mg)9jSKg0Yy zz2Ij6TR@k@!7v;K@Dbc#t|v2r!(qad)eySjjC2^?lOce^VO&vubl!Lhiyq+<69Ixm zjEEA1@;IEtaJoRq4M~^=uc?*6I-Un{z+WOngu)>@B!Gkv7bXQM0)-M|gExeXCsV){ zv!|ROWH1E`HbN4_D# z!=&hnsi1Llc-|BN1ug-?WN1<-U!o!~3?qXlqCP?NL=l;O*+u}%-$2=hQ<R;U<(#_5x)UD7`Qa39Bu{=W*69P6FEd7j|o!*HdXji6gMc43gPxE66z0GB_@Ix zCb%Pw9KfQG9UC@ELqyiBSdc=31KYh&5Zp?{AqdUy2u;umdj`BPui*Frqk8^3afsaH zk-Wui7VQq&F!+gOYyd-u1xzG8J@^$94Q?Xt;a3P4lOWiZ5A1aU(4X+ZeH`dzd>}t4 z9tr?Z1mp!EA1DSuaOr=&WW!)eFvUKXEP^DWx0`|3LMGfB!rKJ61dfnRp zA(IKXX-M@C1oqQk$g@|^5jMhl)fi?C$7eFAbbb^w2yTd|KKaNYaUfU&{P4(2fVWIu z_#_@j$n6t^0|QMGL)ypsgu~QBkT5_)g(4Ji{OD{t4-BNhAPYee8C=PDp(sLzL*SSI zRuU0B{-?{20j;CYK!XUsZ%8mdsqbKfqc8;#l%zgbs#H3Y89`4M1n~VMf`cU8oxCKt z53)ishd0B6$w-n&N=;OFe@QSXIi5yOqzIAjS0R=J(sBM0u7vbA{eqG9I10`PjMG4! z-P#aX)b_Lx7*=7VAHu>IkP_Utz>P!PYEX_qMRY;9E2Il7&VMb^e=X8~Ez*B2(tj<| ze=X8~Ez*B2(tj<|e=X8~Ezdw zLUp1Uy15d^raPyzxGcJWLQH2d*?i}87ej=&Gl1b>D?=iJB1myDM7&mrF~OlkFAk4R zw6nCapaKIb(ca#YY-?w4Pd0}Qs@7H{@Xy|Y41CD!ovj^+;>!@^#-k-Vhk5&o^8#-! zhT=k{rKMS>*;sOTNmgVhCnuPOwY3Gnu;6E~1(b9PHh+YKf;XK{P#D_IOA z4}>jPkO+G;sx%oFESiyIG^!PyNoN6LIUl4TOL0LyVXy&;FHO>?C=%X~MI&D}RdDd1 zQ?OW4Y`(xJ6-e(-%EgCQz*b>&(Pn~AD&TX3q&N&FQwU7*6kwhln1~EQABnT*GzJA2 zi)B+WQW^9#3l>hNURPk`3(fe9%tmfV5N* zorCB}6lz95XoQzHBZOvX7&$m!mhyRyMT< za0cb@_@Nv=12(0*81~r`lEw6=Q)o@0Ai)--U=9sXu#pW9@bUs%CW#CtOd4$a5CeR??8#2{_7>Kb zK#D-4oIxr^Vg@W|M8Y-}aIZ>^kof0{Tq{MWNKF7s71$h*^wY>**Fe6$V&b54r-A*1 ze@~t+Mg6Uxry)_&(%|mV8Q!^KGQl>1kirD!O1Q%W`v}q?ViFwIFHI1KN)f5?FFM+X zwpdrZ5XnY{8xm<$=R^*VMFG_aDkF(*HH}M8Ldq)YLY<@O35eFg-K>k@uf}~@++eWz z2rG63PY4(Ul@T25?9HJH;j{tXE`~xOgXU~+P4c!S`Fc3oksN)mUW_DVz|HvSh$%NjCN*2aw#$nq==tCOd+ySVyv>kCVNR4T$lTq!rnG zDa`*MbD56U85EoccKf6nhivE=5+z-$Gt#F6&FSM{j%ZN7-BOt}f2b)bU0C37*9imw zo`yuwDLaSLc?=43ypWXu_bsWdAWM6*RO2tI#lu z@gAXK8Ub*-2^Vs?Ot52*yud2U7Y7iLW5)YDX7H&@4j=F%Ee@Ef z44kKjpGD;OKnWGN7XFJ&2g@=IjQ8+p;9^L&w6+A*EE;K@;VlHv%8`K>hJ6`yCaq6w zaF8TE7@lyz1EUBDOjLWvW<(sw)QrLu_wNikUp$xfq$G(UCc_o!5p5L*KqoDsVH1!r_cSVi4fs(_YwdvqSc(3#6QcmpVw3W zOnQYYr3>-^1B+w{{jW3nUvP~3J@b7T2D2#fq%M*VhO7uWk0t4n|5;!nIS_?#N+MX} zu=u~p_BXkRW`q6=u*BnK3{dCb#2;8uJA;lH=`;D1R66K&MS<`Fr2kZq1SWrEvV$$j zK{OR~vi2nTI(Yip`}mS=ylg?2DoUFM9C5@;Ei$}1jUbc3aXn|Utp&*roUxLi@i~ct zG%$GqgEBnN6&Y=07Xx;dWTN!spClDHOvp}xXS`O1aDOLiQ~%YH{PU!?UrBzqq!FS0 zV@diPVf;-=kajC>Y5i+XI!z6w1Lq@2SJuD%%4mnUt3g}?e+@?b1P2HPz8o^4vJCyo zfTXjBC!3N9HZt1NY2s>;opVS>NEkg_n85t!#S@JA>%v?%TQHLZo{v;y3JHdFZ%Z%B z-f|a*;r&5 z^RGL!e(Rh5btLXN2cj<A~-cDnnQ{KM$TD4a|W; zc)-U9%mfC?R@8oF&7^$@gun}Vu~LEQp`#PYf&`ZIG7~PimOVlwmXGnsf}Fw>9ashn ztW@BP)K?`W$N}us@nGo&&*l2BdpttCB>r^cIRZeTpKS>gq%W5d|E1}LjI|(KbmH2x z?=%4s^8j_~5hBT`Z}y0Q1|HOdk%=Y|$OWu-Wa?M8Y5$HGf+JHkpx8)|%yI!SYJ=$v zpDtP+N(`^C{ktz&3Fok4{qw*V$^o`ocujZ19^=}ndtc&(`$YSEZDLZ8S6ak#j|A%DvKc!KKh{dbSKGpC`FCrTY_sd{* zoDuMkgATw9;Uxuti9=`*Y=!GbB|t!D!I7ZLv+(frLVjR?)BKvC6{{vGcktly(M)7bM4kinr z@Bd>v=r2gbve?HeD3-4599d+4?1Lqm8p^JH2edE(<6@YfM`U<-FH_<6;g{DSj4gU!HsBBA{Z!~-+K1y1ZBL2BL`{Du?5ypeS%$3a_a9${C2oi+fGAlulVY6>aU>2nVi!hr`7FkBYfN3Xg zKa#Xk6l)QREx7ujAl6bB!+!SAK4pju114~>#S;v4;7dQSDHTy}*%lzl^v~jwU;#lP zeYnglP7(xMKVVSmbK)xT4f*^0D}lcf_$z_G68I~DzY_TWNCLfHt8_Ln6Qlv3)Sjox zF~B)0J~S-EHz3Fdaj;eP4+72xXmGTX1sud+Z>gAxu|)iBaI{kaoH+%DI4M*QpWd9N%senW z22PNH1@v^lCpc>_vBwfeQAZPj+pQzfpPt5~3j`LSz?GQ7qX9=>7MH@#0H6Is$_%H1 zG=6bcLxM%OKSs*n{RxEYRuunv0#auvom;(Oy(xRcI0fKjGB|;aS=k$wumOTj&VwM` z$Gu@j+rcLj=0Z@(9hvgz!1^Md1;cL03_8^k7O3?4M+7P6?*WiQppi$t&y*1Yyg*1@snbgT6tX;5&(UlnP1{H5fGvWsEXIS)&|Lt|%W=5Go8c5j7Q+gkqzFs9C67 z)DrMv(v7GcsJ*BosMDy6sOzXI)FV_g>NV;UssoKdE2Fj0L(wDAR%l1G2RaZPfu4*` zLQhA}K+i=lN3Ta0p!cIo&==7+(GSo~=(p&v;4_v=7#+-Tj0MIKD3C;oMkDG{_hMS37 zira!agu8&N!ac)%#pCgV@n(1zdxmE?!XTg&^($I7$i zbL8{oi{;PD-;;kS->E=QFjjC@2vMLZWGJjqC{#G3a7W>VLZ_m-qKTr5Vua!}#caj( ziU$?1DAp@}R#H+jRB}?9pu|v`qqJV>u+lZ9W~Cp>8p@{1UdnOGLgnSkMambHA1QxU zQBfJG;;s^_B2ZbbvRCDj$`h6Es+y{nssXAr)hyLbswJxTR6nRGs2Qtys7+Cup_Z?9 zOzn=^JAwjXB*BY7APZ_+-e-K2xj z8L1PX!_--!b5y5Br(@92K^}wXgBA@sIOyJ>cHJSm?z(i{#kz-dALw=rCJy!)oHBUT z;FE(J^ssuSdK2{0^tS3<(R(*UYsi=()FF$993AplAFXeyAErN3f2aN}{jWm}hWZWV z4&5;H($IGXItK0r(+t)aoHuwqOmmp)FvhSo!^(!eA!-vnh)m*o;$>pnaQ)%_!-d0l z48J|R)6m2)(lE#HkYU3J#SxAp5=X2VQ9h#0XqZv3(JZ5VMvsjZj2(@Wjn^4h7`Kl! z85uot!N`*%UzzBd1enY)*=JHeN@bMWsOh5$M%^Ec8|^qcW%QQOcTLfzcBaXun@sPR zq0Q{g7-n0{s?6ogoy^(h1?IID$`&3LsTTVznk{uKgDi6_Pg;JkGO~)bT4hyX)lIS^ zF-e7_ImzT5q!$7qjf9{W8$c#=GM zo+mupy_~%=y)JlTy#2fvd*Ala@QL)<=<~$a&^O665Qd(VN1YaxPvio)Uj- zs@&A*sl`)&QbH&Nl&=W^37Zqzs9w}{)VDNu+8Wwxx(hvz{xZ=eF)#5|l1tL+q}R!A z$@$6e7+#ExjE~d&r)`_oo)VH$l+weDW*%b6v!=36vo+W$?8_VjPCBQGYsQ_=ZJO>p zeeLu%ULbEbAH|={KPAutlc`(6QNnq`=2X|zO{w40qSB6~tEaQmZ)KQfEXsH}!*|B6 znb?`sndP&F&&rW7b!tC;_5m|GyUd-{EvnN|IJ0-g^hm@0-^CdSr_sra( zbLY%`HqUR~zWJ*2dGl)*j9IX4A$B2S;q65>i#9CkUQAnjV+m-R^4lxyS8QF0TghHov&wDNo;>xu8F|lE2d^$!LtL|X&6l_q=5Jf4xK6mP zX?@W8QyYvntlaQ(WAeran>;ri+N{5M(dPCov@Q3xx^F$WZRoZo+d8&0wm;nAx1*$B zWI_H;+|JaUErn5q6}udF?cJ@nd&%yuB6d;Jp0GWa_d4v|TdZHaVjp^+aNq0war^Hc z@IG+rpvA!*hjb1tISd^Z9)5FV>XDkGfk(@aIUYNB-1zwB6PhO$m!L{AO4?2)pKLlc z@l@66ai`1Aj5%|n)Uveb?C`Uj&S{@pabEHK-19wUGs?bR;9hulG3ny7^7!()OEH)3 zU!HLJ_LaaZ*RJ|ry;R{*QFhJc+S%*Q*H7PYym9iT{mqhFcDGJc+Et#oZGXGuj>DZ( zcb)E@sTx~#{+`>ti`CxMSMLYhzxiPNgQ}XynuoPJt2oO-$X z#SMgp{3rTP3K~Z@9%{00I@j#oeCuiW)4FHWXK$bLpZ|O@w?(BT|K+fkdtX_DaJ<#M^;TO<+l!C9k3FB3ejfa}=!@-_@~@#^pL}C| z>ug{Aeem}^KOBElbVPT&>`eQK|GBQP5geHZWRD3NhLjzc>{V6P37Ui> z7n?n&hY>&W3hs@dVl`y5hv`n$1i2wuVbP+j0F~i@27yAHo=sY{BuUc6gA87gV*~r`$)tXh0@9LdSS=wr9@N}us3w>So zhb+(>6S8c#GEf5=eHwTaz$JFVLJcJ;{Uvp(z2*~uqUEndg7zu3{|7cye+ zmMz9@HlLQ}Wbo29*I57MrtAKmLi?%XZFg+8eLH^9vOM{l9LsW#!IOu170hN=X_e1n z-Ai8jsE}EF?endSnk8fE(DgdwgQ`+aJbn?f{u`ABc$WuqN6Dcrt90d3T<_g*a9+P~ z|1dL6BOM}PpLq&7$!MHX;;Q~0h2+Wf3lY32)V^#|tyl~U#Uv`v<=H#feY zm}DN#8LHP>UR8W0D+X%f7S5RQUMY0iu<%3ilP$~_ml)7pl490$^+5ILF-tG~RO#r{ zir%$4_Pd^8oPOAeM;%8y>Z4a2t`eB6@E<(1GWI0;?9WYs&N%JTz~x`6e9Y)(?uS!n zl)KSBuZg+w-t@qOdwIK37O!49Ufuka*{}=6oVQ1Fl)KlhDEGh}PI24u;(N*a*`oy8 z5Q(5+iF}*>()H1XZy^_M87SYv*CdWSKmFC^%N0w5X83(s<*29=$>{>e35 z);LNEKR6@!yV<@OpNdoW4tq=vP9L2;bnijKcWZ*)PClx0NG&%zZnER7)o#=3=9{*W z_+O&S_Bz?!G#bur3AZjT;!#F=nka|j4m%#O#f+J<<>8d?U2Eg#5!OxkysP43ppVMI z;CxrJ9w^9Iqbr%QeB0dxE^mc8J51z)G(F{RzJAn$$h(vzO6^^C0w4mak7KE?%iI z+@j+h@$a@2c}@1+a&1W2?yrNr_PNp9y&qLCHXkxJY7fil=@KXFvp?CXe(c?Pznl z*#lOlcSNUEM!F!Oq^ol){jlst|qMAI%v0@{N8B|!#FKgF-|s%JA`;bTiE*Z zx0ddp+&%X-VWOsv<*4_Lr_&5dHiIuMH(U;$eCEa}gB7<5-*iQ5y=dp%D5YocBHz|* zH@>ZL)1Gi?W=BriWOupeGnHE`+TU8F*Am_x%x(N!JAFhPFUs`$&kA)P;c~0=kjKN` zL2@7V(YuZm%-o3ATHl>$M9*6rpH_Rn{M(h&ub)nQq%&&X;{E%-ym?{GeHNfJZ~fyU zm5r01L@#{6-G6pjaox11+p}*xaC~A{lotBop{nUaW^_b-xueEfg;gIka`MYf^AA^Y zU0fXIzaI9{T8;hyk5jafPq+UN+ljrDUiX;TzGuU*d0Kah^W{$`;6A2~yQWupnLF8F z+x8`Il`Y)$v>!~q+tj(>s9)*)&ykj;arKll<{U2NasHe&lQ)*n)?GDWw}Wc(n5U*L zi&YCcURE|fUARo?!qa&-;}-1oP7XWC3VSi(qPF(Zhbc$)kssWK+RAFl!xfXA4MtCw zb00m4xvW9)EXO>>ytFB*W{GxobERg)KEJ&y_tT4B(mOZIu^#u7)zVeTXm~h}Ubl3% zeA`L~@+@wJrJJ?r2+AU6Q*OkX~Dt0E^S}?pj<74)bMXU1$RUEW#44IjKFlB4I^Ff-)Pq%MbmWfV3 z{kkkOvj^9;ZVd_P45~3OB5Ll_*%6`!THBd&T^-?39aPFK(`TJ~n&LcdM77U7oIPqTUuJ^f`1<%h_$F z-|z1+(EeC<_mH+}^sA%IrqSiQ#;tvkTbUlc_%rXt#gAcU&93|`UQk~4{SHVA{#OI> zP)?v*PbPdE?^|u+W}!h??s}JolN)}sTke1^@8`1c9X~$FUE8>taq>tH^s^-Ev$R7X zTx-dmvt~(()10w!Io&j;C5yJ7lGjxqtcclUj@ws`e&MixyWIG*dvj(wDA5n1I`wfV zJ~aQK?!#!Oax~U_d;X<$`Pi%Ow7qsoT&388u`4ltmz&q)e4rA7`?Hc$1t^Xm+C1S# zgz-Z39Iu>*ru5j1*Kfe=>ev(<8-)=k(toML+_xm z5Z6<^Mf=-Ijf*Ersi+Mna`q3M-<-T2DtQ%sc+S+B>Dx)TBy-3EdsEw8fua4VxM>0^ z5tSQhGGcXmLA~cAlE#F?HpDUSbC0)KPRdZsL4E0gmMzqodNnr!XLM|p{G0vRzW$>R zpI;riAaq=*E-uFRThJ&qm(ZF>lr8H1;7a@=#Um)joJm_xl@tb#DpR?RdZ?KK;hn7) z(hH9sGx&ra>QQI9S^3@3C-gj}&qc?Bpu|%JP0D*n#r5v;)5FS3i>D-7sIVoYI7P}c5L5DapTT8c%1t1wrb=sY2zTQZ9y9mjeYf7w#jE> zLloEI=<_vpr5CNdJwdBv(x}C8+M^dWr|2Fzesp5f6ive74YyvbB;+lbM37-4v?K(hZq6ZgKXy<}NR3>mlr&Fm}-8xR?S1-?-6( zD!;p;&zi=a&LS)OYThpQ#JXx@M{7~$&GFo2Y)qde_Z=H^WJ%x!`tHP83S)zRJe*2V T-a933_K>p0hwf9K^*s7NDw=0I literal 0 HcmV?d00001 diff --git a/tika-exiftool/src/test/resources/test-documents/testPNG_IPTC.png b/tika-exiftool/src/test/resources/test-documents/testPNG_IPTC.png new file mode 100644 index 0000000000000000000000000000000000000000..2d9c8f83c5369fce1d9061081d2c31fd0e154522 GIT binary patch literal 18511 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 Yxpj->dF~5;^o!GAe)hAU|Mac@1!ewI2LJ#7 literal 0 HcmV?d00001 diff --git a/tika-exiftool/src/test/resources/test-documents/testTXT.txt b/tika-exiftool/src/test/resources/test-documents/testTXT.txt new file mode 100644 index 00000000000..0b5605a6689 --- /dev/null +++ b/tika-exiftool/src/test/resources/test-documents/testTXT.txt @@ -0,0 +1,2 @@ +Test d'indexation de Txt +http://www.apache.org From 5ff139d68bebd39382d5ed9626bff42797ece01d Mon Sep 17 00:00:00 2001 From: rgauss Date: Sat, 19 Mar 2016 18:44:00 -0400 Subject: [PATCH 2/6] Added git ignore of properties override --- tika-exiftool/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 tika-exiftool/.gitignore diff --git a/tika-exiftool/.gitignore b/tika-exiftool/.gitignore new file mode 100644 index 00000000000..2856187ff45 --- /dev/null +++ b/tika-exiftool/.gitignore @@ -0,0 +1 @@ +tika.exiftool.override.properties \ No newline at end of file From e8a2fa30b16f8b947d118b61ca12476420e9bee0 Mon Sep 17 00:00:00 2001 From: rgauss Date: Mon, 21 Mar 2016 17:24:29 -0400 Subject: [PATCH 3/6] TIKA-774: ExifTool Parser - Moved tika-exiftool from separate project to parsers - Updated license headers - Removed author Javadoc - Fixed a few forbiddenapi violations --- .gitignore | 1 + tika-exiftool/.gitignore | 1 - tika-exiftool/README.md | 16 -- tika-exiftool/pom.xml | 183 ------------------ .../src/test/resources/log4j.properties | 24 --- .../resources/test-documents/testJPEG.jpg | Bin 7686 -> 0 bytes .../test/resources/test-documents/testTXT.txt | 2 - .../exiftool/ExiftoolExternalEmbedder.java | 4 - .../AbstractExiftoolMetadataExtractor.java | 9 +- .../tika/parser/exiftool/ExecutableUtils.java | 10 +- .../parser/exiftool/ExifToolMetadata.java | 3 - .../parser/exiftool/ExiftoolImageParser.java | 4 - .../ExiftoolIptcMetadataExtractor.java | 3 - .../exiftool/ExiftoolTikaIptcMapper.java | 5 - .../parser/exiftool/ExiftoolTikaMapper.java | 4 - .../parser/exiftool/tika.exiftool.properties | 0 .../ExiftoolExternalEmbedderTest.java | 5 - .../exiftool/ExifToolImageParserTest.java | 13 +- .../test-documents/testJPEG_IPTC_EXT.jpg | Bin .../resources/test-documents/testPNG_IPTC.png | Bin .../test-documents/testTIFF_IPTC.tif | Bin 21 files changed, 9 insertions(+), 278 deletions(-) delete mode 100644 tika-exiftool/.gitignore delete mode 100644 tika-exiftool/README.md delete mode 100644 tika-exiftool/pom.xml delete mode 100644 tika-exiftool/src/test/resources/log4j.properties delete mode 100644 tika-exiftool/src/test/resources/test-documents/testJPEG.jpg delete mode 100644 tika-exiftool/src/test/resources/test-documents/testTXT.txt rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java (97%) rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java (97%) rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java (95%) rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java (99%) rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java (98%) rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java (99%) rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java (99%) rename {tika-exiftool => tika-parsers}/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java (96%) rename {tika-exiftool => tika-parsers}/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties (100%) rename {tika-exiftool => tika-parsers}/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java (97%) rename {tika-exiftool => tika-parsers}/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java (98%) rename {tika-exiftool => tika-parsers}/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg (100%) rename {tika-exiftool => tika-parsers}/src/test/resources/test-documents/testPNG_IPTC.png (100%) rename {tika-exiftool => tika-parsers}/src/test/resources/test-documents/testTIFF_IPTC.tif (100%) diff --git a/.gitignore b/.gitignore index 40c895f3f90..2b9a23162f7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ target nbactions.xml nb-configuration.xml *.DS_Store +tika.exiftool.override.properties \ No newline at end of file diff --git a/tika-exiftool/.gitignore b/tika-exiftool/.gitignore deleted file mode 100644 index 2856187ff45..00000000000 --- a/tika-exiftool/.gitignore +++ /dev/null @@ -1 +0,0 @@ -tika.exiftool.override.properties \ No newline at end of file diff --git a/tika-exiftool/README.md b/tika-exiftool/README.md deleted file mode 100644 index 3ec4eeb59f9..00000000000 --- a/tika-exiftool/README.md +++ /dev/null @@ -1,16 +0,0 @@ -Overview -======== - -tika-exifool is an [Apache Tika](http://tika.apache.org/) external parser -which invokes the command line [ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/) -and can map the output to specific Tika metadata fields. - -An IPTC extractor and mapping to Tika's IPTC metadata is provided. - - -Requirements -============ - -ExifTool must be installed. You can specify the full path to the command line in -`org/apache/tika/parser/exiftool/tika.exiftool.properties` or -`org/apache/tika/parser/exiftool/tika.exiftool.override.properties`. \ No newline at end of file diff --git a/tika-exiftool/pom.xml b/tika-exiftool/pom.xml deleted file mode 100644 index 6ce747adb99..00000000000 --- a/tika-exiftool/pom.xml +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - 4.0.0 - - org.apache.tika - tika-exiftool - jar - Apache Tika ExifTool - 1.6-20160315 - http://tika.apache.org/ - - - 1.5 - 1.5 - UTF-8 - 1.6 - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - Ray Gauss II - rgauss - Alfresco - http://alfresco.com - -5 - - - - - https://github.com/Alfresco/tika-exiftool - GitHub Issues - - - - https://github.com/Alfresco/tika-exiftool - scm:git:git://github.com/Alfresco/tika-exiftool.git - scm:git:git@github.com:Alfresco/tika-exiftool.git - - - - - apache.snapshots - http://repository.apache.org/snapshots/ - - true - daily - - - - - - - ${project.groupId} - tika-core - ${tika.version} - - - ${project.groupId} - tika-parsers - ${tika.version} - - - commons-logging - commons-logging - 1.1.1 - provided - - - - - junit - junit - 4.10 - test - - - org.slf4j - slf4j-log4j12 - 1.5.6 - test - - - ${project.groupId} - tika-core - ${tika.version} - test-jar - test - - - ${project.groupId} - tika-parsers - ${tika.version} - test-jar - test - - - - - - - maven-compiler-plugin - 2.3.2 - - ${maven.compile.source} - ${maven.compile.target} - - - - - - - org.apache.felix - maven-bundle-plugin - 2.3.4 - - - - - - src/main/resources - - **/tika.exiftool.override.properties - - - - - - src/test/resources - - **/* - - - - src/main/resources - - **/* - - - - - - - - alfresco-thirdparty - https://artifacts.alfresco.com/nexus/content/repositories/thirdparty - - - alfresco-thirdparty - https://artifacts.alfresco.com/nexus/content/repositories/thirdparty - - - - diff --git a/tika-exiftool/src/test/resources/log4j.properties b/tika-exiftool/src/test/resources/log4j.properties deleted file mode 100644 index 3f8968f2cd1..00000000000 --- a/tika-exiftool/src/test/resources/log4j.properties +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#info,debug, error,fatal ... -log4j.rootLogger=debug,stdout - -#console -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n diff --git a/tika-exiftool/src/test/resources/test-documents/testJPEG.jpg b/tika-exiftool/src/test/resources/test-documents/testJPEG.jpg deleted file mode 100644 index 09d45d460b76ae3172d9afd0009a3991ca91649a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7686 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 Y2z1rZcf;NjN32c@=%*TyrCrVa58cx?$^ZZW diff --git a/tika-exiftool/src/test/resources/test-documents/testTXT.txt b/tika-exiftool/src/test/resources/test-documents/testTXT.txt deleted file mode 100644 index 0b5605a6689..00000000000 --- a/tika-exiftool/src/test/resources/test-documents/testTXT.txt +++ /dev/null @@ -1,2 +0,0 @@ -Test d'indexation de Txt -http://www.apache.org diff --git a/tika-exiftool/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java b/tika-parsers/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java similarity index 97% rename from tika-exiftool/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java rename to tika-parsers/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java index c59d51e7cf4..9938bfaa945 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java +++ b/tika-parsers/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -31,9 +29,7 @@ /** * Convenience class to programmatically create an {@link ExternalEmbedder} which uses ExifTool. * - * @author rgauss * @see ExifTool - * */ public class ExiftoolExternalEmbedder extends ExternalEmbedder { diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java similarity index 97% rename from tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java index aaf9a9df0b1..cec9976d64f 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -26,7 +24,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -55,16 +52,14 @@ * ExifTool command line is required and the path to that executable is defined by exiftool.executable * in tika.exiftool.properties or tika.exiftool.override.properties. * - * @author rgauss * @see ExifTool - * */ public abstract class AbstractExiftoolMetadataExtractor { private static final Log logger = LogFactory.getLog(AbstractExiftoolMetadataExtractor.class); private static final String DEFAULT_CHARSET = "UTF-8"; - private static final MessageFormat FORMAT_EXIFTOOL_XMP_NAMESPACE = new MessageFormat("http://ns.exiftool.ca/XMP/{0}/1.0/"); + private static final String FORMAT_EXIFTOOL_XMP_NAMESPACE = "http://ns.exiftool.ca/XMP/{0}/1.0/"; private final Metadata metadata; private final Set supportedTypes; @@ -208,7 +203,7 @@ public QName getQName(Property exiftoolProperty) { if (passthroughXmpProperties != null && isValidXmpPropertyName(exiftoolProperty.getName()) && passthroughXmpProperties.contains(exiftoolProperty)) { prefix = exiftoolProperty.getName().split(ExifToolMetadata.PREFIX_DELIMITER)[0]; - namespaceUrl = FORMAT_EXIFTOOL_XMP_NAMESPACE.format(new Object[] { prefix }); + namespaceUrl = FORMAT_EXIFTOOL_XMP_NAMESPACE.replaceFirst("\\{0\\}", prefix); name = exiftoolProperty.getName().split(ExifToolMetadata.PREFIX_DELIMITER)[1]; } } diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java similarity index 95% rename from tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java index d7f0f0de1af..d9a4f917b72 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -28,13 +26,9 @@ /** * Class for getting the exiftool command line executable * path from properties or an override parameter. - * - * @author rgauss */ public class ExecutableUtils { - /** - * Logger for this class - */ + private static final Log logger = LogFactory.getLog(ExecutableUtils.class); private static final String DEFAULT_EXIFTOOL_EXECUTABLE = "exiftool"; @@ -65,7 +59,7 @@ public static final String getExiftoolExecutable(String runtimeExiftoolExecutabl if (stream == null) { stream = ExecutableUtils.class.getResourceAsStream(PROPERTIES_FILE); } - if(stream != null){ + if (stream != null) { try { Properties props = new Properties(); props.load(stream); diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java similarity index 99% rename from tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java index 2476b1c4914..4e85873df41 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExifToolMetadata.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -23,7 +21,6 @@ /** * A collection of ExifTool metadata names. * - * @author rgauss * @see ExifTool Tag Names */ public interface ExifToolMetadata { diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java similarity index 98% rename from tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java index 470926494b0..026f01a232b 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolImageParser.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -47,8 +45,6 @@ /** * A parser which uses the {@link ExiftoolIptcMetadataExtractor} to perform the work of metadata extraction. * - * @author rgauss - * */ public class ExiftoolImageParser extends AbstractParser { diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java similarity index 99% rename from tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java index 2adb5fe2f24..274ed46dab4 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -34,7 +32,6 @@ /** * An IPTC extractor implementation of AbstractExiftoolMetadataExtractor - * */ public class ExiftoolIptcMetadataExtractor extends AbstractExiftoolMetadataExtractor { diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java similarity index 99% rename from tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java index e5f12e3d504..99499cb55fd 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaIptcMapper.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -30,9 +28,6 @@ /** * Class for mapping Tika IPTC metadata names to and from ExifTool metadata names. - * - * @author rgauss - * */ public class ExiftoolTikaIptcMapper implements ExiftoolTikaMapper { diff --git a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java similarity index 96% rename from tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java index 7e7a9e6dd11..adada3cc3e2 100644 --- a/tika-exiftool/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolTikaMapper.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -26,8 +24,6 @@ /** * Defines method to convert to and from Tika metadata properties * and ExifTool metadata fields. - * - * @author rgauss */ public interface ExiftoolTikaMapper { diff --git a/tika-exiftool/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties b/tika-parsers/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties similarity index 100% rename from tika-exiftool/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties rename to tika-parsers/src/main/resources/org/apache/tika/parser/exiftool/tika.exiftool.properties diff --git a/tika-exiftool/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java b/tika-parsers/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java similarity index 97% rename from tika-exiftool/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java rename to tika-parsers/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java index 65ec6117b49..001639608d9 100644 --- a/tika-exiftool/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java +++ b/tika-parsers/src/test/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedderTest.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -36,9 +34,6 @@ /** * Unit test for the ExiftoolExternalEmbedder - * - * @author rgauss - * */ public class ExiftoolExternalEmbedderTest extends ExternalEmbedderTest { diff --git a/tika-exiftool/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java b/tika-parsers/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java similarity index 98% rename from tika-exiftool/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java rename to tika-parsers/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java index 37cefa974a4..45ab37dfb2d 100644 --- a/tika-exiftool/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java +++ b/tika-parsers/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java @@ -1,6 +1,4 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. - * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -27,6 +25,7 @@ import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; +import java.util.Locale; import java.util.TimeZone; import junit.framework.TestCase; @@ -40,11 +39,8 @@ import org.xml.sax.helpers.DefaultHandler; public class ExifToolImageParserTest extends TestCase { - /** - * Logger for this class - */ - private static final Log logger = LogFactory - .getLog(ExifToolImageParserTest.class); + + private static final Log logger = LogFactory.getLog(ExifToolImageParserTest.class); private final Parser parser = new ExiftoolImageParser(); @@ -93,7 +89,7 @@ public void testJPEGIPTC() throws Exception { assertEquals("Rock Creek Park", metadata.get(IPTC.SUBLOCATION)); - GregorianCalendar calendar = new GregorianCalendar(); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.ENGLISH); calendar.set(Calendar.YEAR, 2011); calendar.set(Calendar.MONTH, 7); calendar.set(Calendar.DATE, 31); @@ -101,7 +97,6 @@ public void testJPEGIPTC() throws Exception { calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); - calendar.setTimeZone(TimeZone.getTimeZone("UTC")); assertEquals(calendar.getTime(), metadata.getDate(IPTC.DATE_CREATED)); assertEquals("Ray Gauss II", metadata.get(IPTC.DESCRIPTION_WRITER)); diff --git a/tika-exiftool/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg b/tika-parsers/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg similarity index 100% rename from tika-exiftool/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg rename to tika-parsers/src/test/resources/test-documents/testJPEG_IPTC_EXT.jpg diff --git a/tika-exiftool/src/test/resources/test-documents/testPNG_IPTC.png b/tika-parsers/src/test/resources/test-documents/testPNG_IPTC.png similarity index 100% rename from tika-exiftool/src/test/resources/test-documents/testPNG_IPTC.png rename to tika-parsers/src/test/resources/test-documents/testPNG_IPTC.png diff --git a/tika-exiftool/src/test/resources/test-documents/testTIFF_IPTC.tif b/tika-parsers/src/test/resources/test-documents/testTIFF_IPTC.tif similarity index 100% rename from tika-exiftool/src/test/resources/test-documents/testTIFF_IPTC.tif rename to tika-parsers/src/test/resources/test-documents/testTIFF_IPTC.tif From 37aae337c5ca3b5a45c2e45804e3768e08a8bbb6 Mon Sep 17 00:00:00 2001 From: rgauss Date: Mon, 21 Mar 2016 17:31:31 -0400 Subject: [PATCH 4/6] TIKA-774: ExifTool Parser - Removed more author Javadocs --- .../parser/exiftool/AbstractExiftoolMetadataExtractor.java | 3 --- .../tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java | 3 --- 2 files changed, 6 deletions(-) diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java index cec9976d64f..00008f3138c 100644 --- a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java @@ -216,9 +216,6 @@ public QName getQName(Property exiftoolProperty) { /** * Extension of XMLParser which provides a {@link ContentHandler} which * recognizes ExifTool's namespaces and elements. - * - * @author rgauss - * */ public abstract class AbstractExiftoolXmlParser extends XMLParser { diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java index 274ed46dab4..30c11814d1c 100644 --- a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolIptcMetadataExtractor.java @@ -93,9 +93,6 @@ public QName getQName(Property exiftoolProperty) { /** * Extension of XMLParser which provides a {@link ContentHandler} which * recognizes ExifTool's namespaces and elements. - * - * @author rgauss - * */ public class ExiftoolIptcXmlParser extends AbstractExiftoolXmlParser { From 90f8550c03aa873a81975dfa10cfd77aa557fc6f Mon Sep 17 00:00:00 2001 From: rgauss Date: Mon, 21 Mar 2016 18:00:00 -0400 Subject: [PATCH 5/6] TIKA-774: ExifTool Parser - Renamed ExecutableUtils to ExiftoolExecutableUtils - Changed ExifToolImageParserTest to skip when exiftool is not available --- .../exiftool/ExiftoolExternalEmbedder.java | 4 +-- .../AbstractExiftoolMetadataExtractor.java | 2 +- ...tils.java => ExiftoolExecutableUtils.java} | 8 ++--- .../exiftool/ExifToolImageParserTest.java | 30 +++++++++++++++++-- 4 files changed, 34 insertions(+), 10 deletions(-) rename tika-parsers/src/main/java/org/apache/tika/parser/exiftool/{ExecutableUtils.java => ExiftoolExecutableUtils.java} (89%) diff --git a/tika-parsers/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java b/tika-parsers/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java index 9938bfaa945..beb5ec3bee9 100644 --- a/tika-parsers/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java +++ b/tika-parsers/src/main/java/org/apache/tika/embedder/exiftool/ExiftoolExternalEmbedder.java @@ -22,7 +22,7 @@ import org.apache.tika.embedder.ExternalEmbedder; import org.apache.tika.metadata.Property; -import org.apache.tika.parser.exiftool.ExecutableUtils; +import org.apache.tika.parser.exiftool.ExiftoolExecutableUtils; import org.apache.tika.parser.exiftool.ExiftoolTikaMapper; import org.apache.tika.parser.external.ExternalParser; @@ -75,7 +75,7 @@ public void init() { setMetadataCommandArguments(metadataCommandArguments); setExiftoolExecutable( - ExecutableUtils.getExiftoolExecutable(runtimeExiftoolExecutable)); + ExiftoolExecutableUtils.getExiftoolExecutable(runtimeExiftoolExecutable)); setCommandAppendOperator(COMMAND_APPEND_OPERATOR); } diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java index 00008f3138c..b28c963a814 100644 --- a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/AbstractExiftoolMetadataExtractor.java @@ -105,7 +105,7 @@ public AbstractExiftoolMetadataExtractor(Metadata metadata, Set suppo protected Parser getFileParser() { ExternalParser parser = new ExternalParser(); parser.setCommand(new String[] { - ExecutableUtils.getExiftoolExecutable(runtimeExiftoolExecutable), + ExiftoolExecutableUtils.getExiftoolExecutable(runtimeExiftoolExecutable), "-X", ExternalParser.INPUT_FILE_TOKEN }); parser.setSupportedTypes(supportedTypes); diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolExecutableUtils.java similarity index 89% rename from tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java rename to tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolExecutableUtils.java index d9a4f917b72..662a16a1bce 100644 --- a/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExecutableUtils.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/exiftool/ExiftoolExecutableUtils.java @@ -27,9 +27,9 @@ * Class for getting the exiftool command line executable * path from properties or an override parameter. */ -public class ExecutableUtils { +public class ExiftoolExecutableUtils { - private static final Log logger = LogFactory.getLog(ExecutableUtils.class); + private static final Log logger = LogFactory.getLog(ExiftoolExecutableUtils.class); private static final String DEFAULT_EXIFTOOL_EXECUTABLE = "exiftool"; @@ -55,9 +55,9 @@ public static final String getExiftoolExecutable(String runtimeExiftoolExecutabl } String executable = DEFAULT_EXIFTOOL_EXECUTABLE; InputStream stream; - stream = ExecutableUtils.class.getResourceAsStream(PROPERTIES_OVERRIDE_FILE); + stream = ExiftoolExecutableUtils.class.getResourceAsStream(PROPERTIES_OVERRIDE_FILE); if (stream == null) { - stream = ExecutableUtils.class.getResourceAsStream(PROPERTIES_FILE); + stream = ExiftoolExecutableUtils.class.getResourceAsStream(PROPERTIES_FILE); } if (stream != null) { try { diff --git a/tika-parsers/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java b/tika-parsers/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java index 45ab37dfb2d..c4b2f5de3fc 100644 --- a/tika-parsers/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java +++ b/tika-parsers/src/test/java/org/apache/tika/parser/exiftool/ExifToolImageParserTest.java @@ -16,6 +16,10 @@ */ package org.apache.tika.parser.exiftool; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,22 +32,38 @@ import java.util.Locale; import java.util.TimeZone; -import junit.framework.TestCase; - +import org.apache.tika.TikaTest; import org.apache.tika.metadata.IPTC; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Property; import org.apache.tika.metadata.TIFF; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; +import org.apache.tika.parser.external.ExternalParser; +import org.junit.Before; +import org.junit.Test; import org.xml.sax.helpers.DefaultHandler; -public class ExifToolImageParserTest extends TestCase { +public class ExifToolImageParserTest extends TikaTest { private static final Log logger = LogFactory.getLog(ExifToolImageParserTest.class); private final Parser parser = new ExiftoolImageParser(); + + public static boolean canRun() { + String exiftoolCmd = ExiftoolExecutableUtils.getExiftoolExecutable(null); + String[] checkCmd = { exiftoolCmd, "-ver"}; + // If Exiftool is not on the path, do not run the test. + return ExternalParser.check(checkCmd); + } + + @Before + public void setup() + { + assumeTrue(canRun()); + } + @Test public void testJPEGIPTC() throws Exception { Metadata metadata = new Metadata(); metadata.set(Metadata.CONTENT_TYPE, "image/jpeg"); @@ -294,6 +314,7 @@ public void testJPEGIPTC() throws Exception { } + @Test public void testJPEGCustomXmp() throws Exception { Metadata metadata = new Metadata(); metadata.set(Metadata.CONTENT_TYPE, "image/jpeg"); @@ -309,6 +330,7 @@ public void testJPEGCustomXmp() throws Exception { assertEquals("customMultilineField", metadata.get("XMP-custom:TextML")); } + @Test public void testJPEG() throws Exception { Metadata metadata = new Metadata(); metadata.set(Metadata.CONTENT_TYPE, "image/jpeg"); @@ -322,6 +344,7 @@ public void testJPEG() throws Exception { } } + @Test public void testPNGIPTC() throws Exception { Metadata metadata = new Metadata(); metadata.set(Metadata.CONTENT_TYPE, "image/png"); @@ -336,6 +359,7 @@ public void testPNGIPTC() throws Exception { assertEquals("Cat in a garden", metadata.get(IPTC.HEADLINE)); } + @Test public void testTIFFIPTC() throws Exception { Metadata metadata = new Metadata(); metadata.set(Metadata.CONTENT_TYPE, "image/tiff"); From 969440e32bf267a8189df6dd4fddf43bffcc4751 Mon Sep 17 00:00:00 2001 From: rgauss Date: Mon, 21 Mar 2016 19:47:29 -0400 Subject: [PATCH 6/6] Added note on merge path --- tika-exiftool/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tika-exiftool/README.md b/tika-exiftool/README.md index 3ec4eeb59f9..fe6c1d2d7d1 100644 --- a/tika-exiftool/README.md +++ b/tika-exiftool/README.md @@ -1,3 +1,10 @@ +Important +========= + +This code has been contributed to Apache in TIKA-774 and this should only serve as a maintenance branch. +Any changes should also make their way into Apache, likely through the `tika-exiftool` branch +and then as a pull request to Apache's master branch. + Overview ========