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;
024
025import org.apache.commons.io.FilenameUtils;
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.ecm.core.api.Blob;
029import org.nuxeo.ecm.core.api.Blobs;
030import org.nuxeo.ecm.platform.commandline.executor.api.CommandAvailability;
031import org.nuxeo.ecm.platform.commandline.executor.api.CommandException;
032import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService;
033import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable;
034import org.nuxeo.ecm.platform.picture.core.ImageUtils;
035import org.nuxeo.ecm.platform.picture.magick.utils.ImageConverter;
036import org.nuxeo.ecm.platform.picture.magick.utils.ImageCropper;
037import org.nuxeo.ecm.platform.picture.magick.utils.ImageIdentifier;
038import org.nuxeo.ecm.platform.picture.magick.utils.ImageResizer;
039import org.nuxeo.ecm.platform.picture.magick.utils.ImageRotater;
040import org.nuxeo.runtime.api.Framework;
041
042public class IMImageUtils implements ImageUtils {
043
044    private static final Log log = LogFactory.getLog(IMImageUtils.class);
045
046    public static abstract class ImageMagickCaller {
047
048        protected File sourceFile;
049
050        // a tmp file is needed if the blob doesn't have a file, or
051        // if it has one but with an incorrect extension
052        protected File tmpFile;
053
054        protected File targetFile;
055
056        public Blob call(Blob blob, String targetExt, String commandName) {
057            CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class);
058            CommandAvailability availability = cles.getCommandAvailability(commandName);
059            if (!availability.isAvailable()) {
060                return null;
061            }
062
063            try {
064                makeFiles(blob, targetExt);
065
066                callImageMagick();
067
068                Blob targetBlob = Blobs.createBlob(targetFile);
069                Framework.trackFile(targetFile, targetBlob);
070                return targetBlob;
071            } catch (CommandNotAvailable | CommandException | IOException e) {
072                log.error("ImageMagick failed on command: " + commandName, e);
073                return null;
074            } finally {
075                if (tmpFile != null) {
076                    tmpFile.delete();
077                }
078            }
079        }
080
081        protected void makeFiles(Blob blob, String targetExt) throws CommandNotAvailable, CommandException, IOException {
082            sourceFile = blob.getFile();
083
084            // check extension
085            String ext = FilenameUtils.getExtension(blob.getFilename());
086            if (ext == null || "".equals(ext)) {
087                // no known extension
088                if (sourceFile == null) {
089                    sourceFile = createTempSource(blob, "tmp");
090                }
091                // detect extension
092                ext = ImageIdentifier.getInfo(sourceFile.getPath()).getFormat();
093                if (tmpFile == null) {
094                    // copy source with proper name
095                    sourceFile = createTempSource(blob, ext);
096                } else {
097                    // rename tmp file
098                    File newTmpFile = new File(FilenameUtils.removeExtension(tmpFile.getPath()) + "." + ext);
099                    tmpFile.renameTo(newTmpFile);
100                    tmpFile = newTmpFile;
101                    sourceFile = newTmpFile;
102                }
103            } else {
104                // check that extension on source is correct
105                if (sourceFile != null && !ext.equals(FilenameUtils.getExtension(sourceFile.getName()))) {
106                    sourceFile = null;
107                }
108            }
109
110            if (sourceFile == null) {
111                sourceFile = createTempSource(blob, ext);
112            }
113
114            if (targetExt == null) {
115                targetExt = ext;
116            }
117            targetFile = Framework.createTempFile("nuxeoImageTarget", "." + targetExt);
118        }
119
120        protected File createTempSource(Blob blob, String ext) throws IOException {
121            tmpFile = Framework.createTempFile("nuxeoImageSource", "." + ext);
122            blob.transferTo(tmpFile);
123            return tmpFile;
124        }
125
126        public abstract void callImageMagick() throws CommandNotAvailable, CommandException;
127    }
128
129    @Override
130    public Blob crop(Blob blob, final int x, final int y, final int width, final int height) {
131        return new ImageMagickCaller() {
132            @Override
133            public void callImageMagick() throws CommandNotAvailable, CommandException {
134                ImageCropper.crop(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), width, height, x, y);
135            }
136        }.call(blob, null, "resizer");
137    }
138
139    @Override
140    public Blob resize(Blob blob, String finalFormat, final int width, final int height, final int depth) {
141        return new ImageMagickCaller() {
142            @Override
143            public void callImageMagick() throws CommandNotAvailable, CommandException {
144                ImageResizer.resize(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), width, height, depth);
145            }
146        }.call(blob, finalFormat, "resizer");
147    }
148
149    @Override
150    public Blob rotate(Blob blob, final int angle) {
151        return new ImageMagickCaller() {
152            @Override
153            public void callImageMagick() throws CommandNotAvailable, CommandException {
154                ImageRotater.rotate(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), angle);
155            }
156        }.call(blob, null, "rotate");
157    }
158
159    @Override
160    public Blob convertToPDF(Blob blob) {
161        return new ImageMagickCaller() {
162            @Override
163            public void callImageMagick() throws CommandNotAvailable, CommandException {
164                ImageConverter.convert(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
165            }
166        }.call(blob, "pdf", "converter");
167    }
168
169    @Override
170    public boolean isAvailable() {
171        CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class);
172        CommandAvailability commandAvailability = cles.getCommandAvailability("identify");
173        return commandAvailability.isAvailable();
174    }
175
176}