org.apache.tika.parser.image.PSDParser Maven / Gradle / Ivy
/*
* 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.image;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.poi.util.IOUtils;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.EndianUtils;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Photoshop;
import org.apache.tika.metadata.TIFF;
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.sax.XHTMLContentHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import static java.nio.charset.StandardCharsets.US_ASCII;
/**
* Parser for the Adobe Photoshop PSD File Format.
*
* Documentation on the file format is available from
* http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm
*/
public class PSDParser extends AbstractParser {
/**
* Serial version UID
*/
private static final long serialVersionUID = 883387734607994914L;
private static final Set SUPPORTED_TYPES =
Collections.unmodifiableSet(new HashSet(Arrays.asList(
MediaType.image("vnd.adobe.photoshop"))));
public Set getSupportedTypes(ParseContext context) {
return SUPPORTED_TYPES;
}
public void parse(
InputStream stream, ContentHandler handler,
Metadata metadata, ParseContext context)
throws IOException, SAXException, TikaException {
// Check for the magic header signature
byte[] signature = new byte[4];
IOUtils.readFully(stream, signature);
if (signature[0] == (byte) '8' && signature[1] == (byte) 'B' &&
signature[2] == (byte) 'P' && signature[3] == (byte) 'S') {
// Good, signature found
} else {
throw new TikaException("PSD/PSB magic signature invalid");
}
// Check the version
int version = EndianUtils.readUShortBE(stream);
if (version == 1 || version == 2) {
// Good, we support these two
} else {
throw new TikaException("Invalid PSD/PSB version " + version);
}
// Skip the reserved block
IOUtils.readFully(stream, new byte[6]);
// Number of channels in the image
int numChannels = EndianUtils.readUShortBE(stream);
// TODO Identify a suitable metadata key for this
// Width and Height
int height = EndianUtils.readIntBE(stream);
int width = EndianUtils.readIntBE(stream);
metadata.set(TIFF.IMAGE_LENGTH, height);
metadata.set(TIFF.IMAGE_WIDTH, width);
// Depth (bits per channel)
int depth = EndianUtils.readUShortBE(stream);
metadata.set(TIFF.BITS_PER_SAMPLE, Integer.toString(depth));
// Colour mode, eg Bitmap or RGB
int colorMode = EndianUtils.readUShortBE(stream);
metadata.set(Photoshop.COLOR_MODE, Photoshop._COLOR_MODE_CHOICES_INDEXED[colorMode]);
// Next is the Color Mode section
// We don't care about this bit
long colorModeSectionSize = EndianUtils.readIntBE(stream);
stream.skip(colorModeSectionSize);
// Next is the Image Resources section
// Check for certain interesting keys here
long imageResourcesSectionSize = EndianUtils.readIntBE(stream);
long read = 0;
while (read < imageResourcesSectionSize) {
ResourceBlock rb = new ResourceBlock(stream);
read += rb.totalLength;
// Is it one we can do something useful with?
if (rb.id == ResourceBlock.ID_CAPTION) {
metadata.add(TikaCoreProperties.DESCRIPTION, rb.getDataAsString());
} else if (rb.id == ResourceBlock.ID_EXIF_1) {
// TODO Parse the EXIF info via ImageMetadataExtractor
} else if (rb.id == ResourceBlock.ID_EXIF_3) {
// TODO Parse the EXIF info via ImageMetadataExtractor
} else if (rb.id == ResourceBlock.ID_XMP) {
// TODO Parse the XMP info via ImageMetadataExtractor
}
}
// Next is the Layer and Mask Info
// Finally we have Image Data
// We can't do anything with these parts
// We don't have any helpful text, sorry...
XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata);
xhtml.startDocument();
xhtml.endDocument();
}
private static class ResourceBlock {
private static final long SIGNATURE = 0x3842494d; // 8BIM
private static final int ID_CAPTION = 0x03F0;
private static final int ID_URL = 0x040B;
private static final int ID_EXIF_1 = 0x0422;
private static final int ID_EXIF_3 = 0x0423;
private static final int ID_XMP = 0x0424;
private int id;
private String name;
private byte[] data;
private int totalLength;
private ResourceBlock(InputStream stream) throws IOException, TikaException {
// Verify the signature
long sig = EndianUtils.readIntBE(stream);
if (sig != SIGNATURE) {
throw new TikaException("Invalid Image Resource Block Signature Found, got " +
sig + " 0x" + Long.toHexString(sig) + " but the spec defines " + SIGNATURE);
}
// Read the block
id = EndianUtils.readUShortBE(stream);
StringBuffer nameB = new StringBuffer();
int nameLen = 0;
while (true) {
int v = stream.read();
nameLen++;
if (v == 0) {
// The name length is padded to be even
if (nameLen % 2 == 1) {
stream.read();
nameLen++;
}
break;
} else {
nameB.append((char) v);
}
name = nameB.toString();
}
int dataLen = EndianUtils.readIntBE(stream);
if (dataLen % 2 == 1) {
// Data Length is even padded
dataLen = dataLen + 1;
}
totalLength = 4 + 2 + nameLen + 4 + dataLen;
data = new byte[dataLen];
IOUtils.readFully(stream, data);
}
private String getDataAsString() {
// Will be null padded
return new String(data, 0, data.length - 1, US_ASCII);
}
}
}