blob: a42c785b5352587c0072ec936b25909360cef2d8 [file] [log] [blame]
/*
* 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.drill.exec.store.image;
import java.time.Instant;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.TimeZone;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.exec.store.image.ImageBatchReader.ColumnDefn;
import org.apache.drill.exec.store.image.ImageBatchReader.ListColumnDefn;
import org.apache.drill.exec.store.image.ImageBatchReader.MapColumnDefn;
import org.apache.drill.exec.vector.accessor.ArrayWriter;
import org.apache.drill.exec.vector.accessor.ColumnWriter;
import org.apache.drill.exec.vector.accessor.TupleWriter;
import org.slf4j.LoggerFactory;
import com.adobe.internal.xmp.XMPException;
import com.adobe.internal.xmp.XMPIterator;
import com.adobe.internal.xmp.XMPMeta;
import com.adobe.internal.xmp.options.IteratorOptions;
import com.adobe.internal.xmp.properties.XMPPropertyInfo;
import com.drew.lang.KeyValuePair;
import com.drew.lang.Rational;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.StringValue;
import com.drew.metadata.Tag;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import com.drew.metadata.exif.GpsDirectory;
import com.drew.metadata.jpeg.JpegComponent;
import com.drew.metadata.png.PngDirectory;
import com.drew.metadata.xmp.XmpDirectory;
/**
* Although each image format can contain different metadata,
* they also have common basic information. The class handles
* basic metadata as well as complex tags.
* @see org.apache.drill.exec.store.image.GenericMetadataDirectory
* @see com.drew.metadata.Directory
*/
public class ImageDirectoryProcessor {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ImageDirectoryProcessor.class);
protected static void processGenericMetadataDirectory(final GenericMetadataDirectory directory,
final LinkedHashMap<String, ColumnDefn> genericColumns,
final ImageFormatConfig config) {
for (Tag tag : directory.getTags()) {
final int tagType = tag.getTagType();
if (!config.hasFileSystemMetadata() && ImageMetadataUtils.isSkipTag(tag.getTagName())) {
continue;
}
genericColumns.get(ImageMetadataUtils.formatName(tag.getTagName())).load(config.isDescriptive()
? directory.getDescription(tagType)
: directory.getObject(tagType));
}
}
protected static void processXmpDirectory(final MapColumnDefn writer, final XmpDirectory directory) {
XMPMeta xmpMeta = directory.getXMPMeta();
if (xmpMeta != null) {
try {
IteratorOptions iteratorOptions = new IteratorOptions().setJustLeafnodes(true);
for (final XMPIterator i = xmpMeta.iterator(iteratorOptions); i.hasNext();) {
try {
XMPPropertyInfo prop = (XMPPropertyInfo) i.next();
String path = prop.getPath();
String value = prop.getValue();
if (path != null && value != null) {
// handling lang-alt array items
if (prop.getOptions().getHasLanguage()) {
XMPPropertyInfo langProp = (XMPPropertyInfo) i.next();
if (langProp.getPath().endsWith("/xml:lang")) {
String lang = langProp.getValue();
path = path.replaceFirst("\\[\\d+\\]$", "") +
(lang.equals("x-default") ? "" : "_" + lang);
}
}
ColumnDefn rootColumn = writer;
ColumnWriter subColumn = null;
String[] elements = path.replaceAll("/\\w+:", "/").split(":|/|(?=\\[)");
// 1. lookup and create nested structure
for (int j = 1; j < elements.length; j++) {
String parent = elements[j - 1];
boolean isList = elements[j].startsWith("[");
if (!parent.startsWith("[")) { // skipped. such as parent is [1] but not the last element
final String formatName = ImageMetadataUtils.formatName(parent);
if (isList) {
if (j + 1 == elements.length) { // for list
subColumn = rootColumn.addList(formatName);
} else { // for list-map
subColumn = rootColumn.addListMap(formatName);
}
rootColumn = new ListColumnDefn(formatName).builder((ArrayWriter) subColumn);
} else { // for map
subColumn = ((MapColumnDefn) rootColumn).addMap(formatName);
// set up the current writer in nested structure
rootColumn = new MapColumnDefn(formatName).builder((TupleWriter) subColumn);
}
}
}
// 2. set up the value for writer
String parent = elements[elements.length - 1];
if (parent.startsWith("[")) {
subColumn.setObject(new String[] { value });
} else {
rootColumn.addText(ImageMetadataUtils.formatName(parent)).setString(value);
if (subColumn instanceof ArrayWriter) {
((ArrayWriter) subColumn).save();
}
}
}
} catch (Exception skipped) { // simply skip this property
logger.warn("Error in written xmp metadata : {}", skipped.getMessage());
}
}
} catch (XMPException ignored) {
logger.warn("Error in processing xmp directory : {}", ignored.getMessage());
}
}
}
protected static void processDirectory(final MapColumnDefn writer,
final Directory directory,
final Metadata metadata,
final ImageFormatConfig config) {
TimeZone timeZone = (config.getTimeZone() != null)
? TimeZone.getTimeZone(config.getTimeZone())
: TimeZone.getDefault();
for (Tag tag : directory.getTags()) {
try {
final int tagType = tag.getTagType();
Object value;
if (config.isDescriptive() || ImageMetadataUtils.isDescriptionTag(directory, tagType)) {
value = directory.getDescription(tagType);
if (directory instanceof PngDirectory) {
if (((PngDirectory) directory).getPngChunkType().areMultipleAllowed()) {
value = new String[] { (String) value };
}
}
} else {
value = directory.getObject(tagType);
if (directory instanceof ExifIFD0Directory && tagType == ExifIFD0Directory.TAG_DATETIME) {
ExifSubIFDDirectory exifSubIFDDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
String subsecond = null;
if (exifSubIFDDir != null) {
subsecond = exifSubIFDDir.getString(ExifSubIFDDirectory.TAG_SUBSECOND_TIME);
}
value = directory.getDate(tagType, subsecond, timeZone);
} else if (directory instanceof ExifSubIFDDirectory) {
if (tagType == ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL) {
value = ((ExifSubIFDDirectory) directory).getDateOriginal(timeZone);
} else if (tagType == ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) {
value = ((ExifSubIFDDirectory) directory).getDateDigitized(timeZone);
}
} else if (directory instanceof GpsDirectory) {
if (tagType == GpsDirectory.TAG_LATITUDE) {
value = ((GpsDirectory) directory).getGeoLocation().getLatitude();
} else if (tagType == GpsDirectory.TAG_LONGITUDE) {
value = ((GpsDirectory) directory).getGeoLocation().getLongitude();
}
}
if (ImageMetadataUtils.isVersionTag(directory, tagType)) {
value = directory.getString(tagType, "US-ASCII");
} else if (ImageMetadataUtils.isDateTag(directory, tagType)) {
value = directory.getDate(tagType, timeZone);
}
}
processValue(writer, ImageMetadataUtils.formatName(tag.getTagName()), value);
} catch (Exception skipped) {
logger.warn("Error in processing image directory : {}", skipped.getMessage());
}
}
}
/**
* Convert the value if necessary
* @see org.apache.drill.exec.vector.accessor.writer.AbstractScalarWriter#setObject(Object)
* @param writer MapColumnDefn
* @param name Tag Name
* @param value Tag Value
*/
protected static void processValue(final MapColumnDefn writer, final String name, final Object value) {
if (value == null) {
return;
}
if (value instanceof Boolean) {
writer.addObject(name, MinorType.BIT).setObject(value);
} else if (value instanceof Byte) {
writer.addObject(name, MinorType.TINYINT).setObject(value);
} else if (value instanceof Short) {
writer.addObject(name, MinorType.SMALLINT).setObject(value);
} else if (value instanceof Integer) {
writer.addObject(name, MinorType.INT).setObject(value);
} else if (value instanceof Long) {
writer.addObject(name, MinorType.BIGINT).setObject(value);
} else if (value instanceof Float) {
writer.addObject(name, MinorType.FLOAT4).setObject(value);
} else if (value instanceof Double) {
writer.addObject(name, MinorType.FLOAT8).setObject(value);
} else if (value instanceof Rational) {
writer.addDouble(name).setDouble(((Rational) value).doubleValue());
} else if (value instanceof StringValue) {
writer.addText(name).setString(((StringValue) value).toString());
} else if (value instanceof Date) {
writer.addDate(name).setTimestamp(Instant.ofEpochMilli(((Date) value).getTime()));
} else if (value instanceof String[]) {
writer.addList(name).setObject(value);
} else if (value instanceof byte[]) {
writer.addListByte(name).setObject(value);
} else if (value instanceof JpegComponent) {
JpegComponent v = (JpegComponent) value;
TupleWriter component = writer.addMap(name);
writer.addIntToMap(component, TagName.JPEGCOMPONENT_CID).setInt(v.getComponentId());
writer.addIntToMap(component, TagName.JPEGCOMPONENT_HSF).setInt(v.getHorizontalSamplingFactor());
writer.addIntToMap(component, TagName.JPEGCOMPONENT_VSF).setInt(v.getVerticalSamplingFactor());
writer.addIntToMap(component, TagName.JPEGCOMPONENT_QTN).setInt(v.getQuantizationTableNumber());
} else if (value instanceof List<?>) {
ArrayWriter listMap = writer.addListMap(name);
ListColumnDefn list = new ListColumnDefn(name).builder(listMap);
for (Object v : (List<?>) value) {
if (v instanceof KeyValuePair) {
list.addText(TagName.KEYVALUEPAIR_K).setString(((KeyValuePair) v).getKey());
list.addText(TagName.KEYVALUEPAIR_V).setString(((KeyValuePair) v).getValue().toString());
} else {
list.addText(TagName.KEYVALUEPAIR_V).setString(v.toString());
}
listMap.save();
}
} else {
writer.addText(name).setString(value.toString());
}
}
private static class TagName {
public static final String JPEGCOMPONENT_CID = "ComponentId";
public static final String JPEGCOMPONENT_HSF = "HorizontalSamplingFactor";
public static final String JPEGCOMPONENT_VSF = "VerticalSamplingFactor";
public static final String JPEGCOMPONENT_QTN = "QuantizationTableNumber";
public static final String KEYVALUEPAIR_K = "Key";
public static final String KEYVALUEPAIR_V = "Value";
}
}