001/* 002 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Laurent Doguin 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.platform.picture.core.im; 021 022import java.io.File; 023import java.io.IOException; 024import java.nio.file.Files; 025 026import org.apache.commons.io.FilenameUtils; 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.nuxeo.ecm.core.api.Blob; 030import org.nuxeo.ecm.core.api.Blobs; 031import org.nuxeo.ecm.platform.commandline.executor.api.CommandAvailability; 032import org.nuxeo.ecm.platform.commandline.executor.api.CommandException; 033import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService; 034import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable; 035import org.nuxeo.ecm.platform.picture.core.ImageUtils; 036import org.nuxeo.ecm.platform.picture.magick.utils.ImageConverter; 037import org.nuxeo.ecm.platform.picture.magick.utils.ImageCropper; 038import org.nuxeo.ecm.platform.picture.magick.utils.ImageIdentifier; 039import org.nuxeo.ecm.platform.picture.magick.utils.ImageResizer; 040import org.nuxeo.ecm.platform.picture.magick.utils.ImageRotater; 041import org.nuxeo.runtime.api.Framework; 042 043public class IMImageUtils implements ImageUtils { 044 045 private static final Log log = LogFactory.getLog(IMImageUtils.class); 046 047 public abstract static class ImageMagickCaller { 048 049 protected File sourceFile; 050 051 // a tmp file is needed if the blob doesn't have a file, or 052 // if it has one but with an incorrect extension 053 protected File tmpFile; 054 055 protected File targetFile; 056 057 public Blob call(Blob blob, String targetExt, String commandName) { 058 CommandLineExecutorService cles = Framework.getService(CommandLineExecutorService.class); 059 CommandAvailability availability = cles.getCommandAvailability(commandName); 060 if (!availability.isAvailable()) { 061 return null; 062 } 063 064 try { 065 makeFiles(blob, targetExt); 066 067 callImageMagick(); 068 069 Blob targetBlob = Blobs.createBlob(targetFile); 070 targetBlob.setFilename(getFilename(blob, targetExt)); 071 Framework.trackFile(targetFile, targetBlob); 072 return targetBlob; 073 } catch (CommandNotAvailable | CommandException | IOException e) { 074 log.error("ImageMagick failed on command: " + commandName, e); 075 return null; 076 } finally { 077 if (tmpFile != null) { 078 try { 079 Files.delete(tmpFile.toPath()); 080 } catch (IOException e) { 081 log.error("Unable to delete temporary file when calling ImageMagick command: " + commandName, 082 e); 083 } 084 } 085 } 086 } 087 088 protected void makeFiles(Blob blob, String targetExt) 089 throws CommandNotAvailable, CommandException, IOException { 090 sourceFile = blob.getFile(); 091 092 // check extension 093 String ext = FilenameUtils.getExtension(blob.getFilename()); 094 if (ext == null || "".equals(ext)) { 095 // no known extension 096 if (sourceFile == null) { 097 sourceFile = createTempSource(blob, "tmp"); 098 } 099 // detect extension 100 ext = ImageIdentifier.getInfo(sourceFile.getPath()).getFormat(); 101 if (tmpFile == null) { 102 // copy source with proper name 103 sourceFile = createTempSource(blob, ext); 104 } else { 105 // rename tmp file 106 File newTmpFile = new File(FilenameUtils.removeExtension(tmpFile.getPath()) + "." + ext); 107 Files.move(tmpFile.toPath(), newTmpFile.toPath()); 108 tmpFile = newTmpFile; 109 sourceFile = newTmpFile; 110 } 111 } else { 112 // check that extension on source is correct 113 if (sourceFile != null && !ext.equals(FilenameUtils.getExtension(sourceFile.getName()))) { 114 sourceFile = null; 115 } 116 } 117 118 if (sourceFile == null) { 119 sourceFile = createTempSource(blob, ext); 120 } 121 122 if (targetExt == null) { 123 targetExt = ext; 124 } 125 targetFile = Framework.createTempFile("nuxeoImageTarget", "." + targetExt); 126 } 127 128 protected File createTempSource(Blob blob, String ext) throws IOException { 129 tmpFile = Framework.createTempFile("nuxeoImageSource", "." + ext); 130 blob.transferTo(tmpFile); 131 return tmpFile; 132 } 133 134 protected String getFilename(Blob blob, String targetExt) { 135 String baseName = FilenameUtils.getBaseName(blob.getFilename()); 136 return baseName + "." + targetExt; 137 } 138 139 public abstract void callImageMagick() throws CommandNotAvailable, CommandException; 140 } 141 142 @Override 143 public Blob crop(Blob blob, final int x, final int y, final int width, final int height) { 144 return new ImageMagickCaller() { 145 @Override 146 public void callImageMagick() throws CommandNotAvailable, CommandException { 147 ImageCropper.crop(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), width, height, x, y); 148 } 149 }.call(blob, null, "resizer"); 150 } 151 152 @Override 153 public Blob resize(Blob blob, String finalFormat, final int width, final int height, final int depth) { 154 return new ImageMagickCaller() { 155 @Override 156 public void callImageMagick() throws CommandNotAvailable, CommandException { 157 ImageResizer.resize(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), width, height, depth); 158 } 159 }.call(blob, finalFormat, "resizer"); 160 } 161 162 @Override 163 public Blob rotate(Blob blob, final int angle) { 164 return new ImageMagickCaller() { 165 @Override 166 public void callImageMagick() throws CommandNotAvailable, CommandException { 167 ImageRotater.rotate(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), angle); 168 } 169 }.call(blob, null, "rotate"); 170 } 171 172 @Override 173 public Blob convertToPDF(Blob blob) { 174 return new ImageMagickCaller() { 175 @Override 176 public void callImageMagick() throws CommandNotAvailable, CommandException { 177 ImageConverter.convert(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath()); 178 } 179 }.call(blob, "pdf", "converter"); 180 } 181 182 @Override 183 public boolean isAvailable() { 184 CommandLineExecutorService cles = Framework.getService(CommandLineExecutorService.class); 185 CommandAvailability commandAvailability = cles.getCommandAvailability("identify"); 186 return commandAvailability.isAvailable(); 187 } 188 189}