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}