com.github.geko444.im4java.core.Info Maven / Gradle / Ivy
/**************************************************************************
/* This class implements an image-information object.
/*
/* Copyright (c) 2009-2013 by Bernhard Bablok ([email protected])
/* Mihaly Koles
/*
/* This program is free software; you can redistribute it and/or modify
/* it under the terms of the GNU Library General Public License as published
/* by the Free Software Foundation; either version 2 of the License or
/* (at your option) any later version.
/*
/* This program is distributed in the hope that it will be useful, but
/* WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU Library General Public License for more details.
/*
/* You should have received a copy of the GNU Library General Public License
/* along with this program; see the file COPYING.LIB. If not, write to
/* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
/* Boston, MA 02111-1307 USA
/**************************************************************************/
package com.github.geko444.im4java.core;
import java.util.*;
import java.io.*;
import com.github.geko444.im4java.process.ArrayListOutputConsumer;
import com.github.geko444.im4java.process.Pipe;
/**
This class implements an image-information object. The one-argument
constructor expects a filename and parses the output of the
"identify -verbose" command to create a hashtable of properties. This
is the so called complete information. The two-argument constructor
has a boolean flag as second argument. If you pass true, the Info-object
only creates a set of so called basic information. This is more
efficient since only a subset of the attributes of the image are
requested and parsed.
Since the output of "identify -verbose" is meant as a human-readable
interface and not for parsing, this class is inherently flawed.
This implementation
interprets every line with a colon as a key-value-pair. This is not
necessarely correct, e.g. the comment-field could be multi-line with
colons within the comment.
An alternative to the Info-class is to use the exiftool-command and
the wrapper for it provided by im4java.
@version $Revision: 1.15 $
@author $Author: bablokb $
@since 0.95
*/
public class Info {
//////////////////////////////////////////////////////////////////////////////
/**
Internal hashtable with image-attributes. For images with multiple
scenes, this hashtable holds the attributes of the last scene.
*/
private Hashtable iAttributes = null;
//////////////////////////////////////////////////////////////////////////////
/**
List of hashtables with image-attributes. There is one element for every
scene in the image.
@since 1.3.0
*/
private LinkedList> iAttribList =
new LinkedList>();
//////////////////////////////////////////////////////////////////////////////
/**
Current value of indentation level
*/
private int iOldIndent=0;
//////////////////////////////////////////////////////////////////////////////
/**
Current value of attribute-prefix
*/
private String iPrefix="";
//////////////////////////////////////////////////////////////////////////////
/**
This contstructor will automatically parse the full output
of identify -verbose.
@param pImage Source image
@since 0.95
*/
public Info(String pImage) throws InfoException {
getCompleteInfo(pImage,null);
}
//////////////////////////////////////////////////////////////////////////////
/**
This contstructor will automatically parse the full output
of identify -verbose. This version of the constructor expects an
InputStream as an additional parameter. The source image-name should
be either "-" or "format:-".
@param pImage Name of the source-image
@param pInput Image provided as an InputStream
@since 1.4.0
*/
public Info(String pImage, InputStream pInput) throws InfoException {
if (pInput != null && !(pImage.equals("-") || pImage.endsWith(":-"))) {
throw new IllegalArgumentException("illegal filename for piped input");
}
getCompleteInfo(pImage,pInput);
}
//////////////////////////////////////////////////////////////////////////////
/**
This constructor creates an Info-object with basic or complete
image-information (depending on the second argument).
@param pImage Source image
@param basic Set to true for basic information, to false for complete info
@since 1.2.0
*/
public Info(String pImage, boolean basic) throws InfoException {
if (!basic) {
getCompleteInfo(pImage,null);
} else {
getBaseInfo(pImage,null);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
This constructor creates an Info-object with basic or complete
image-information (depending on the third argument). This version
of the constructor expects an
InputStream as an additional parameter. The source image-name should
be either "-" or "format:-".
@param pImage Source image
@param basic Set to true for basic information, to false for complete info
@param pInput Image provided as an InputStream
@since 1.4.0
*/
public Info(String pImage, InputStream pInput,
boolean basic) throws InfoException {
if (pInput != null && !(pImage.equals("-") || pImage.endsWith(":-"))) {
throw new IllegalArgumentException("illegal filename for piped input");
}
if (!basic) {
getCompleteInfo(pImage,pInput);
} else {
getBaseInfo(pImage,pInput);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Query complete image-information.
@param pImage Source image
@param pInput InputStream (must not be null for pImage=="-")
@since 1.2.0
*/
private void getCompleteInfo(String pImage, InputStream pInput)
throws InfoException {
IMOperation op = new IMOperation();
op.verbose();
op.addImage(pImage);
try {
IdentifyCmd identify = new IdentifyCmd();
ArrayListOutputConsumer output = new ArrayListOutputConsumer();
identify.setOutputConsumer(output);
if (pInput != null) {
Pipe inputPipe = new Pipe(pInput,null);
identify.setInputProvider(inputPipe);
}
identify.run(op);
ArrayList cmdOutput = output.getOutput();
StringBuilder lineAccu = new StringBuilder(80);
for (String line:cmdOutput) {
if (line.length() == 0) {
// accumulate empty line as part of current attribute
lineAccu.append("\n\n");
} else if (line.indexOf(':') == -1) {
// interpret this as a continuation-line of the current attribute
lineAccu.append("\n").append(line);
} else if (lineAccu.length() > 0) {
// new attribute, process old attribute first
parseLine(lineAccu.toString());
lineAccu = new StringBuilder(80);
lineAccu.append(line);
} else {
// new attribute, but nothing old to process
lineAccu.append(line);
}
}
// process last item
if (lineAccu.length() > 0) {
parseLine(lineAccu.toString());
}
// finish and add last hashtable to linked-list
addBaseInfo();
iAttribList.add(iAttributes);
} catch (Exception ex) {
throw new InfoException(ex);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Add basic info to complete-info.
@since 1.3.0
*/
private void addBaseInfo() {
// complete output does not include width, height, depth
String[] dim;
String geo = iAttributes.get("Geometry");
if (geo != null) {
dim = geo.split("x|\\+");
iAttributes.put("Width",dim[0]);
iAttributes.put("Height",dim[1]);
}
geo = iAttributes.get("Page geometry");
if (geo != null) {
dim = geo.split("x|\\+");
iAttributes.put("PageWidth",dim[0]);
iAttributes.put("PageHeight",dim[1]);
iAttributes.put("PageGeometry",geo);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Parse line of identify-output
*/
private void parseLine(String pLine) {
// structure:
// indent attribute: value
if (pLine.startsWith("Image:")) {
// start of a new scene
if (iAttributes != null) {
addBaseInfo();
iAttribList.add(iAttributes);
}
iAttributes = new Hashtable();
}
int indent = pLine.indexOf(pLine.trim())/2;
String[] parts = pLine.trim().split(": ",2);
// check indentation level and remove prefix if necessary
if (indent < iOldIndent) {
// remove tokens from iPrefix
int colonIndex=iPrefix.length()-1;
for (int i=0;i add attribute to attribute-prefix
iPrefix=iPrefix+parts[0];
} else {
// value => add (key,value) to attributes
iAttributes.put(iPrefix+parts[0],parts[1]);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Query basic image-information.
@param pImage Source image
@param pInput InputStream (must not be null for pImage=="-")
@since 1.2.0
*/
private void getBaseInfo(String pImage, InputStream pInput) throws InfoException {
// create operation
IMOperation op = new IMOperation();
op.ping();
op.format("%m\n%w\n%h\n%g\n%W\n%H\n%G\n%z\n%r");
op.addImage(pImage);
try {
// execute ...
IdentifyCmd identify = new IdentifyCmd();
ArrayListOutputConsumer output = new ArrayListOutputConsumer();
identify.setOutputConsumer(output);
if (pInput != null) {
Pipe inputPipe = new Pipe(pInput,null);
identify.setInputProvider(inputPipe);
}
identify.run(op);
// ... and parse result
ArrayList cmdOutput = output.getOutput();
Iterator iter = cmdOutput.iterator();
iAttributes = new Hashtable();
iAttributes.put("Format",iter.next());
iAttributes.put("Width",iter.next());
iAttributes.put("Height",iter.next());
iAttributes.put("Geometry",iter.next());
iAttributes.put("PageWidth",iter.next());
iAttributes.put("PageHeight",iter.next());
iAttributes.put("PageGeometry",iter.next());
iAttributes.put("Depth",iter.next());
iAttributes.put("Class",iter.next());
iAttribList.add(iAttributes);
} catch (Exception ex) {
throw new InfoException(ex);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image format.
*/
public String getImageFormat() {
return iAttributes.get("Format");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image format for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public String getImageFormat(int pSceneNr) {
return iAttribList.get(pSceneNr).get("Format");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image width.
*/
public int getImageWidth() throws InfoException {
return getImageWidth(iAttribList.size()-1);
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image width for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public int getImageWidth(int pSceneNr) throws InfoException {
try {
return Integer.parseInt(iAttribList.get(pSceneNr).get("Width"));
} catch (NumberFormatException ex) {
throw new InfoException(ex);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image height.
*/
public int getImageHeight() throws InfoException {
return getImageHeight(iAttribList.size()-1);
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image height for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public int getImageHeight(int pSceneNr) throws InfoException {
try {
return Integer.parseInt(iAttribList.get(pSceneNr).get("Height"));
} catch (NumberFormatException ex) {
throw new InfoException(ex);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image geometry.
*/
public String getImageGeometry() {
return iAttributes.get("Geometry");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image geometry for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public String getImageGeometry(int pSceneNr) {
return iAttribList.get(pSceneNr).get("Geometry");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image depth. Note that this method just returns an integer
(e.g. 8 or 16), and not a string ("8-bit") like getProperty("Depth")
does.
@since 1.3.0
*/
public int getImageDepth() throws InfoException {
return getImageDepth(iAttribList.size()-1);
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image depth. Note that this method just returns an integer
(e.g. 8 or 16), and not a string ("8-bit") like getProperty("Depth")
does.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public int getImageDepth(int pSceneNr) throws InfoException {
String[] depth = iAttribList.get(pSceneNr).get("Depth").split("-|/",2);
try {
return Integer.parseInt(depth[0]);
} catch (NumberFormatException ex) {
throw new InfoException(ex);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image class.
*/
public String getImageClass() {
return iAttributes.get("Class");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the image class for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public String getImageClass(int pSceneNr) {
return iAttribList.get(pSceneNr).get("Class");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the page width.
@since 1.3.0
*/
public int getPageWidth() throws InfoException {
return getPageWidth(iAttribList.size()-1);
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the page width for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public int getPageWidth(int pSceneNr) throws InfoException {
try {
return Integer.parseInt(iAttribList.get(pSceneNr).get("PageWidth"));
} catch (NumberFormatException ex) {
throw new InfoException(ex);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the page height.
@since 1.3.0
*/
public int getPageHeight() throws InfoException {
return getPageHeight(iAttribList.size()-1);
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the page height for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public int getPageHeight(int pSceneNr) throws InfoException {
try {
return Integer.parseInt(iAttribList.get(pSceneNr).get("PageHeight"));
} catch (NumberFormatException ex) {
throw new InfoException(ex);
}
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the page geometry.
@since 1.3.0
*/
public String getPageGeometry() {
return iAttributes.get("PageGeometry");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the page geometry for the given scene.
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public String getPageGeometry(int pSceneNr) {
return iAttribList.get(pSceneNr).get("PageGeometry");
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the given property.
*/
public String getProperty(String pPropertyName) {
return iAttributes.get(pPropertyName);
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the given property of the given scene.
@param pPropertyName Name of the property
@param pSceneNr Scene-number (zero-based)
@since 1.3.0
*/
public String getProperty(String pPropertyName, int pSceneNr) {
return iAttribList.get(pSceneNr).get(pPropertyName);
}
//////////////////////////////////////////////////////////////////////////////
/**
Return the number of scenes.
@since 1.3.0
*/
public int getSceneCount() {
return iAttribList.size();
}
//////////////////////////////////////////////////////////////////////////////
/**
Return an enumeration of all properties.
*/
public Enumeration getPropertyNames() {
return iAttributes.keys();
}
}