*/
package mir.media.image;
-import mir.config.MirPropertiesConfiguration;
import mir.log.LoggerWrapper;
import mir.media.MediaExc;
-import mir.util.ShellRoutines;
+import mir.media.MediaFailure;
import mir.util.StreamCopier;
+import mir.util.ShellRoutines;
+import mir.config.MirPropertiesConfiguration;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
* Image processing by calling the ImageMagick command line progrmas
* "convert" and "identify". The main task of this class is to scale
* images. The path to ImageMagick commandline programs can be
- * specified in the configuration file.
+ * specified in the coonfiguration file.
*
* @author <grok@no-log.org>, the Mir-coders group
*/
private ImageFile sourceImage;
private ImageFile scaledImage;
+ /**
+ * ImageFile is a thin wrapper around a file that contains an
+ * image. It uses ImageMagick to retreive information about the
+ * image. It can also scale images using ImageMagick. Intended for
+ * use in the ImageMagickImageProcessor class.
+ */
+ static class ImageFile {
+ /**
+ * path to the file represented by this class
+ */
+ File file;
+ /**
+ * whether the file must be deleted on cleanup
+ */
+ boolean fileIsTemp = false;
+ /**
+ * image information is stored here to avoid multiple costly calls to
+ * "identify"
+ */
+ int width;
+ int height;
+ int fileSize;
+
+ /**
+ * Image type as returned by identify %m : "PNG", "GIF", ...
+ */
+ String type;
+ /**
+ * number of scenes in image >1 (typically animated gif)
+ */
+ boolean isAnimation;
+
+ /**
+ * Empty constructor automatically creates a temporary file
+ * that will later hold an image
+ */
+ ImageFile() throws IOException {
+ file = File.createTempFile("mirimage", "");
+ fileIsTemp = true;
+ }
+
+ /**
+ * if the file doesn't already have an image in it
+ * we don't want to read its information
+ */
+ ImageFile(File file, boolean doReadInfo) throws IOException {
+ this.file = file;
+ if (doReadInfo) {
+ readInfo();
+ }
+ }
+
+ ImageFile(File file) throws IOException {
+ this(file, true);
+ }
+
+ /**
+ * delete temporary files
+ */
+ public void cleanup() {
+ logger.debug("ImageFile.cleanup()");
+ if (fileIsTemp) {
+ logger.debug("deleting:" + file);
+ file.delete();
+ file = null;
+ fileIsTemp = false;
+ }
+ }
+
+ void debugOutput() {
+ logger.debug(" filename:" + file +
+ " Info:" +
+ " width:" + width +
+ " height:" + height +
+ " type:" + type +
+ " isAnimation:" + isAnimation);
+ }
+
+ private void checkFile() throws IOException {
+ if (file == null || !file.exists()) {
+ String message = "ImageFile.checkFile file \"" + file +
+ "\" does not exist";
+ logger.error(message);
+ throw new IOException(message);
+ }
+ }
+
+ /**
+ * Uses the imagemagick "identify" command to retreive image information
+ */
+ public void readInfo() throws IOException {
+ checkFile();
+ String infoString = ShellRoutines.execIntoString
+ (getImageMagickPath() +
+ "identify " + "-format \"%w %h %m %n %b \" " +
+ file.getAbsolutePath()); // extra space, for multiframe (animations)
+ StringTokenizer st = new StringTokenizer(infoString);
+ width = Integer.parseInt(st.nextToken());
+ height = Integer.parseInt(st.nextToken());
+ type = st.nextToken();
+ isAnimation = Integer.parseInt(st.nextToken()) > 1;
+ // yoss: different versions of ImageMagick produce different formatted output file sizes
+ // for example, Version: ImageMagick 6.2.4 (Ubuntu Dapper 6.06) produces a byte value, i.e. 67013
+ // Version: ImageMagick 6.0.6 (Debian Sarge) produces output like 67kb or 500mb.
+ // Trying to do an int.parse in Sarge fails for obvious reasons.
+ /*String sFileSize = st.nextToken();
+ if (sFileSize.endsWith("kb") || sFileSize.endsWith("Kb") || sFileSize.endsWith("KB") || sFileSize.endsWith("kB")){
+ sFileSize = sFileSize.substring(0, sFileSize.length() - 2);
+ fileSize = 1024 * Integer.parseInt(sFileSize);
+ } else if (sFileSize.endsWith("mb") || sFileSize.endsWith("Mb") || sFileSize.endsWith("MB") || sFileSize.endsWith("mB")){
+ sFileSize = sFileSize.substring(0, sFileSize.length() - 2);
+ fileSize = 1048576 * Integer.parseInt(sFileSize);
+ } else {
+ fileSize = Integer.parseInt(sFileSize);
+ }*/
+ fileSize = (int)file.length();
+ }
+
+ public ImageFile scale(float aScalingFactor) throws IOException {
+ logger.debug("ImageFile.scale");
+ checkFile();
+ ImageFile result = new ImageFile();
+ String command = getImageMagickPath() + "convert " +
+ file.getAbsolutePath() + " " +
+ "-scale " +
+ Float.toString(aScalingFactor * 100) + "% " +
+ result.file.getAbsolutePath();
+ logger.debug("ImageFile.scale:command:" + command);
+ ShellRoutines.simpleExec(command);
+ result.readInfo();
+ return result;
+ }
+ }
+
public ImageMagickImageProcessor(InputStream inputImageStream)
throws IOException {
logger.debug("ImageMagickImageProcessor(stream)");
if (1 - scale > aMinDescale) {
scaleImage(scale);
-
- return;
+ }
+ } else {
+ logger.debug("descaleImage: image size is ok, not scaling image");
+ try {
+ scaledImage = new ImageFile(sourceImage.file);
+ }
+ catch (IOException e) {
+ throw new MediaExc(e.toString());
}
}
-
- // the image didn't need to be scaled: scaledImage = original image
- try {
- scaledImage = new ImageFile(sourceImage.file);
- }
- catch (IOException e) {
- throw new MediaExc(e.toString());
- }
-
}
/**
* Scales image by a factor using the convert ImageMagick command
*/
- public void scaleImage(float aScalingFactor) throws MediaExc {
+ public void scaleImage(float aScalingFactor)
+ throws MediaExc {
logger.debug("scaleImage:" + aScalingFactor);
try {
// first cleanup previous temp scaledimage file if necesary
public int getHeight() {
return sourceImage.height;
}
+
+ public int getSourceFileSize() {
+ return sourceImage.fileSize;
+ }
+
+ public int getScaledFileSize() {
+ return scaledImage.fileSize;
+ }
public int getScaledWidth() {
return scaledImage.width;
return scaledImage.height;
}
- public void writeScaledData(OutputStream aStream, String anImageType) throws MediaExc, IOException {
+ public void writeScaledData(OutputStream aStream, String anImageType)
+ throws MediaExc {
// we can't asume that requested "anImageType" is the same as the
// scaled image type, so we have to convert
- // if image is an animation and target type doesn't support
- // animations, then just use first frame
- String frame = "";
- scaledImage.debugOutput();
-
- if (scaledImage.isAnimation && !anImageType.equals("GIF")) {
- frame = "[0]";
- }
- // ImageMagick "convert" into temp file
- File temp = File.createTempFile("mirimage", "");
- String command = getImageMagickPath() + "convert " +
- scaledImage.file.getAbsolutePath() + frame + " " +
- anImageType + ":" + temp.getAbsolutePath();
- logger.debug("writeScaledData command:" + command);
- ShellRoutines.simpleExec(command);
- // copy temp file into stream
- StreamCopier.copy(new FileInputStream(temp), aStream);
- temp.delete();
- }
-
- public void writeScaledData(File aFile, String anImageType) throws MediaExc, IOException, FileNotFoundException {
- OutputStream stream = new BufferedOutputStream(new FileOutputStream(aFile), 8192);
-
try {
- writeScaledData(stream, anImageType);
- }
- finally {
- try {
- stream.close();
- }
- catch (Throwable t) {
+ // if image is an animation and target type doesn't support
+ // animations, then just use first frame
+ String frame = "";
+ scaledImage.debugOutput();
+ if (scaledImage.isAnimation && !anImageType.equals("GIF")) {
+ frame = "[0]";
}
+ // ImageMagick "convert" into temp file
+ File temp = File.createTempFile("mirimage", "");
+ String command = getImageMagickPath() + "convert " +
+ scaledImage.file.getAbsolutePath() + frame + " " +
+ anImageType + ":" + temp.getAbsolutePath();
+ logger.debug("writeScaledData command:" + command);
+ ShellRoutines.simpleExec(command);
+ // copy temp file into stream
+ StreamCopier.copy(new FileInputStream(temp), aStream);
+ temp.delete();
}
- }
-
- /**
- * ImageFile is a thin wrapper around a file that contains an
- * image. It uses ImageMagick to retreive information about the
- * image. It can also scale images using ImageMagick. Intended for
- * use in the ImageMagickImageProcessor class.
- */
- static class ImageFile {
- /**
- * path to the file represented by this class
- */
- File file;
- /**
- * whether the file must be deleted on cleanup
- */
- boolean fileIsTemp = false;
- /**
- * image information is stored here to avoid multiple costly calls to
- * "identify"
- */
- int width;
- int height;
- /**
- * Image type as returned by identify %m : "PNG", "GIF", ...
- */
- String type;
- /**
- * number of scenes in image >1 (typically animated gif)
- */
- boolean isAnimation;
-
- /**
- * Empty constructor automatically creates a temporary file
- * that will later hold an image
- */
- ImageFile() throws IOException {
- file = File.createTempFile("mirimage", "");
- fileIsTemp = true;
+ catch (Exception e) {
+ throw new MediaExc(e.toString());
}
+ }
- /**
- * if the file doesn't already have an image in it
- * we don't want to read its information
- */
- ImageFile(File file, boolean doReadInfo) throws IOException {
- this.file = file;
- if (doReadInfo) {
- readInfo();
- }
- }
+ public byte[] getScaledData(String anImageType) throws MediaExc {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ writeScaledData(outputStream, anImageType);
+ return outputStream.toByteArray();
+ }
- ImageFile(File file) throws IOException {
- this(file, true);
- }
+ public void writeScaledData(File aFile, String anImageType) throws MediaExc {
+ try {
+ OutputStream stream = new BufferedOutputStream(new FileOutputStream(aFile), 8192);
- /**
- * delete temporary files
- */
- public void cleanup() {
- logger.debug("ImageFile.cleanup()");
- if (fileIsTemp) {
- logger.debug("deleting:" + file);
- file.delete();
- file = null;
- fileIsTemp = false;
+ try {
+ writeScaledData(stream, anImageType);
}
- }
-
- void debugOutput() {
- logger.debug(" filename:" + file +
- " Info:" +
- " width:" + width +
- " height:" + height +
- " type:" + type +
- " isAnimation:" + isAnimation);
- }
-
- private void checkFile() throws IOException {
- if (file == null || !file.exists()) {
- String message = "ImageFile.checkFile file \"" + file +
- "\" does not exist";
- logger.error(message);
- throw new IOException(message);
+ finally {
+ try {
+ stream.close();
+ }
+ catch (Throwable t) {
+ logger.debug("Unable to close stream when writing scaled data.");
+ }
}
}
-
- /**
- * Uses the imagemagick "identify" command to retreive image information
- */
- public void readInfo() throws IOException {
- checkFile();
- String infoString = ShellRoutines.execIntoString
- (getImageMagickPath() +
- "identify " + "-format \"%w %h %m %n \" " +
- file.getAbsolutePath()); // extra space, for multiframe (animations)
- StringTokenizer st = new StringTokenizer(infoString);
- width = Integer.parseInt(st.nextToken());
- height = Integer.parseInt(st.nextToken());
- type = st.nextToken();
- isAnimation = Integer.parseInt(st.nextToken()) > 1;
+ catch (FileNotFoundException f) {
+ throw new MediaFailure(f);
}
-
- public ImageFile scale(float aScalingFactor) throws IOException {
- logger.debug("ImageFile.scale");
-
- checkFile();
- ImageFile result = new ImageFile();
- String command = getImageMagickPath() + "convert " +
- file.getAbsolutePath() + " " +
- "-scale " +
- Float.toString(aScalingFactor * 100) + "% " +
- result.file.getAbsolutePath();
- logger.debug("ImageFile.scale:command:" + command);
- ShellRoutines.simpleExec(command);
- result.readInfo();
-
- return result;
+ catch (Exception e) {
+ logger.debug("Exception caught while trying to write scaled data: " + e.toString());
}
}
}
package mircoders.media;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import mir.util.StreamCopier;
import mir.entity.Entity;
import mir.log.LoggerWrapper;
import mir.media.MediaExc;
import mir.media.MediaFailure;
-import mir.media.image.ImageMagickImageProcessor;
import mir.media.image.ImageProcessor;
+import mir.media.image.ImageMagickImageProcessor;
import mir.misc.StringUtil;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
/**
* Image handler that stores images outside of the database.
*
* @version 1.0
*/
-public class MediaHandlerImagesExtern extends MediaHandlerGeneric {
+public class MediaHandlerImagesExtern extends MediaHandlerGeneric
+{
+ private int maxSize;
private int maxIconSize;
private float minDescaleRatio;
private int minDescaleReduction;
-
+ private boolean scaleImages;
+ private String imagesOriginalDir;
+ private String imagesOriginalDirRelPath;
+ private String imageOriginalFilePath;
+ private String imageOriginalRelPath;
+
public MediaHandlerImagesExtern() {
-
logger = new LoggerWrapper("Media.Images.Extern");
-
+ maxSize = configuration.getInt("Producer.Image.MaxSize");
maxIconSize = configuration.getInt("Producer.Image.MaxIconSize");
minDescaleRatio = configuration.getFloat("Producer.Image.MinDescalePercentage")/100;
minDescaleReduction = configuration.getInt("Producer.Image.MinDescaleReduction");
+ scaleImages = configuration.getBoolean("Producer.Image.ScaleImages");
+ imagesOriginalDir = configuration.getString("Producer.ImagesOriginalDir.Path");
+ imagesOriginalDirRelPath = configuration.getString("Producer.ImagesOriginalDir.RelPath");
}
- public void produce(Entity anImageEntity, Entity aMediaTypeEntity) throws MediaExc, MediaFailure {
+ public void produce(Entity anImageEntity, Entity mediaTypeEnt) throws MediaExc, MediaFailure {
+ try {
String date = anImageEntity.getFieldValue("date");
String datePath = StringUtil.webdbDate2path(date);
- String ext = "." + aMediaTypeEntity.getFieldValue("name");
+ String ext = "." + mediaTypeEnt.getFieldValue("name");
String fileBasePath = datePath + anImageEntity.getId();
String filePath = fileBasePath + ext;
String iconPath = getBaseIconStoragePath() + fileBasePath + ".jpg";
String iconStoragePath = configuration.getString("Producer.StorageRoot") + iconPath;
String imageFilePath = getBaseStoragePath() + File.separator + filePath;
-
+
+ // yoss: get a file path where the the original image should be saved if image resizing is turned on
+ imageOriginalFilePath = imagesOriginalDir + filePath;
+ imageOriginalRelPath = imagesOriginalDirRelPath + filePath;
+
+ // yoss:make a new File object for the originalFile
+ File originalFile = new File(imageOriginalFilePath);
+ logger.info("imageOriginalFilePath:" + imageOriginalFilePath);
File imageFile = new File(imageFilePath);
+ logger.info("******************************************");
+ logger.info("imageFile exists: " + imageFile.exists());
+ logger.info("imageFile: " + imageFile.getAbsolutePath());
File iconFile = new File(iconStoragePath);
-
+ logger.info("iconStoragePath:"+ iconStoragePath);
+
+
if (!imageFile.exists()) {
throw new MediaExc("error in MediaHandlerImagesExtern.execute(): " + filePath + " does not exist!");
}
else {
- ImageProcessor processor;
- try {
- processor = new ImageMagickImageProcessor(imageFile);
- }
- catch (IOException e) {
- throw new MediaFailure(e);
- }
+ ImageProcessor processor = new ImageMagickImageProcessor(imageFile);
processor.descaleImage(maxIconSize, minDescaleRatio, minDescaleReduction);
File dir = new File(iconFile.getParent());
- if (dir!=null && !dir.exists()){
- dir.mkdirs();
+ if (dir!=null && !dir.exists()){
+ dir.mkdirs();
}
- try {
- processor.writeScaledData(iconFile, "JPEG");
+ processor.writeScaledData(iconFile, "JPEG");
+
+ // yoss: if the config is set so that images should be scaled, make the resized file.
+ if (scaleImages){
+ logger.info("entered scaleImages");
+ ImageProcessor originalProcessor = new ImageMagickImageProcessor(imageFile);
+ originalProcessor.descaleImage(maxSize, minDescaleRatio, minDescaleReduction);
+ File originalDir = new File(originalFile.getParent());
+ if(originalDir!=null && !originalDir.exists()) {
+ originalDir.mkdirs();
+ }
+ if(!originalFile.exists()) {
+ logger.debug("the original image file doesn't exist, copying to originalFile");
+ StreamCopier.copyFile(imageFile, originalFile);
+ }
+ // yoss: don't write the scaled data again if it's the same size as
+ // the file that's there right now. Image producer runs are 4 times faster this way.
+ logger.info("about to write scaled data, byte length: " + originalProcessor.getScaledFileSize());
+ if (originalProcessor.getScaledFileSize() != imageFile.length()) {
+ originalProcessor.writeScaledData(imageFile, "JPEG");
+ }
+ anImageEntity.setFieldValue("original_file_path", imageOriginalRelPath);
+ anImageEntity.setFieldValue("img_height", new Integer(originalProcessor.getScaledHeight()).toString());
+ anImageEntity.setFieldValue("img_width", new Integer(originalProcessor.getScaledWidth()).toString());
+
+ originalProcessor.cleanup();
+
+ } else {
+ anImageEntity.setFieldValue("img_height", new Integer(processor.getHeight()).toString());
+ anImageEntity.setFieldValue("img_width", new Integer(processor.getWidth()).toString());
}
- catch (IOException e) {
- throw new MediaFailure(e);
- }
-
- anImageEntity.setFieldValue("img_height",
- Integer.toString(processor.getHeight()));
- anImageEntity.setFieldValue("img_width",
- Integer.toString(processor.getWidth()));
- anImageEntity.setFieldValue("icon_height",
- Integer.toString(processor.getScaledHeight()));
- anImageEntity.setFieldValue("icon_width",
- Integer.toString(processor.getScaledWidth()));
+ anImageEntity.setFieldValue("icon_height", new Integer(processor.getScaledHeight()).toString());
+ anImageEntity.setFieldValue("icon_width", new Integer(processor.getScaledWidth()).toString());
processor.cleanup();
+
anImageEntity.setFieldValue("icon_path", iconPath);
anImageEntity.setFieldValue("publish_path", filePath);
anImageEntity.update();
+
}
+ }
+ catch(Throwable t) {
+ logger.error("MediaHandlerImagesExtern.execute: " + t.getMessage(), t);
+ throw new MediaFailure(t.getMessage(), t);
+ }
}