19 package org.sleuthkit.autopsy.modules.pictureanalyzer.impls;
21 import java.util.HashSet;
23 import java.util.logging.Level;
24 import java.util.Arrays;
25 import java.util.concurrent.TimeUnit;
27 import java.io.BufferedInputStream;
29 import java.io.IOException;
31 import java.nio.file.DirectoryIteratorException;
32 import java.nio.file.DirectoryStream;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.StandardCopyOption;
37 import java.nio.file.attribute.BasicFileAttributes;
39 import org.apache.commons.io.FilenameUtils;
41 import org.openide.modules.InstalledFileLocator;
42 import org.openide.util.lookup.ServiceProvider;
67 @ServiceProvider(service = PictureProcessor.class)
72 private static final int EXIT_SUCCESS = 0;
73 private static final String HEIC_MODULE_FOLDER =
"HEIC";
74 private static final long TIMEOUT_IN_MS = TimeUnit.MILLISECONDS.convert(2, TimeUnit.MINUTES);
77 private static final String IMAGE_MAGICK_FOLDER =
"ImageMagick-7.0.10-27-portable-Q16-x64";
78 private static final String IMAGE_MAGICK_EXE =
"magick.exe";
79 private static final String IMAGE_MAGICK_ERROR_FILE =
"magick_error.txt";
85 IMAGE_MAGICK_PATH = findImageMagick();
87 if (IMAGE_MAGICK_PATH == null) {
88 logger.log(Level.WARNING,
"ImageMagick executable not found. "
89 +
"HEIC functionality will be automatically disabled.");
94 final Path windowsLocation = Paths.get(IMAGE_MAGICK_FOLDER, IMAGE_MAGICK_EXE);
95 final Path macAndLinuxLocation = Paths.get(
"/usr",
"local",
"bin",
"magick");
100 final File locatedExec = InstalledFileLocator.getDefault().locate(
101 windowsLocation.toString(),
HEICProcessor.class.getPackage().getName(),
false);
103 return (locatedExec != null) ? locatedExec.toPath() : null;
104 }
else if ((osName.equals(
"linux") || osName.startsWith(
"mac")) &&
105 Files.isExecutable(macAndLinuxLocation) &&
106 !Files.isDirectory(macAndLinuxLocation)) {
107 return macAndLinuxLocation;
120 return Paths.get(moduleOutputDirectory,
122 String.valueOf(file.getId()));
129 final Path moduleOutputFolder = getModuleOutputFolder(file);
131 if (!Files.exists(moduleOutputFolder)) {
132 Files.createDirectories(moduleOutputFolder);
139 if (IMAGE_MAGICK_PATH == null) {
142 createModuleOutputFolder(file);
148 final Path localDiskCopy = extractToDisk(file);
150 convertToJPEG(context, localDiskCopy, file);
151 }
catch (IOException ex) {
152 logger.log(Level.WARNING,
"I/O error encountered during HEIC photo processing.", ex);
153 }
catch (TskCoreException ex) {
154 logger.log(Level.SEVERE,
"Unable to add pictures as derived files.", ex);
156 logger.log(Level.WARNING,
"No open case!", ex);
167 final Path localDiskCopy = Paths.get(tempDir, heicFileName);
169 try (BufferedInputStream heicInputStream =
new BufferedInputStream(
new ReadContentInputStream(heicFile))) {
170 Files.copy(heicInputStream, localDiskCopy, StandardCopyOption.REPLACE_EXISTING);
171 return localDiskCopy;
179 final Path moduleOutputFolder = getModuleOutputFolder(heicFile);
182 final Path outputFile = moduleOutputFolder.resolve(baseFileName +
".jpg");
184 final Path imageMagickErrorOutput = moduleOutputFolder.resolve(IMAGE_MAGICK_ERROR_FILE);
185 Files.createFile(imageMagickErrorOutput);
190 final ProcessBuilder processBuilder =
new ProcessBuilder()
191 .command(IMAGE_MAGICK_PATH.toString(),
192 localDiskCopy.toString(),
193 outputFile.toString());
195 processBuilder.redirectError(imageMagickErrorOutput.toFile());
197 final long startTime = System.currentTimeMillis();
199 return context.fileIngestIsCancelled() || System.currentTimeMillis() - startTime >= TIMEOUT_IN_MS;
202 if (context.fileIngestIsCancelled()) {
206 if (exitStatus != EXIT_SUCCESS) {
207 logger.log(Level.INFO,
"Non-zero exit status for HEIC file [id: {0}]. Skipping...", heicFile.getId());
213 final String glob = String.format(
"{%1$s.jpg,%1$s-*.jpg}", baseFileName);
214 try (DirectoryStream<Path> stream = Files.newDirectoryStream(moduleOutputFolder, glob)) {
217 for (Path candidate : stream) {
218 if (context.fileIngestIsCancelled()) {
222 final BasicFileAttributes attrs = Files.readAttributes(candidate, BasicFileAttributes.class);
223 final Path localCasePath = caseDirectory.relativize(candidate);
226 .addDerivedFile(candidate.getFileName().toString(),
227 localCasePath.toString(), attrs.size(), 0L,
228 attrs.creationTime().to(TimeUnit.SECONDS),
229 attrs.lastAccessTime().to(TimeUnit.SECONDS),
230 attrs.lastModifiedTime().to(TimeUnit.SECONDS),
231 attrs.isRegularFile(), heicFile,
"",
232 "",
"",
"", TskData.EncodingType.NONE);
234 context.addFilesToJob(Arrays.asList(jpegFile));
238 }
catch (DirectoryIteratorException ex) {
245 return new HashSet<String>() {
void createModuleOutputFolder(AbstractFile file)
static int execute(ProcessBuilder processBuilder)
String getTempDirectory()
Path getModuleOutputFolder(AbstractFile file)
String getCaseDirectory()
Path extractToDisk(AbstractFile heicFile)
Set< String > mimeTypes()
final Path IMAGE_MAGICK_PATH
SleuthkitCase getSleuthkitCase()
String getModuleDirectory()
boolean fileIngestIsCancelled()
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
void convertToJPEG(IngestJobContext context, Path localDiskCopy, AbstractFile heicFile)
static String escapeFileName(String fileName)
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
void process(IngestJobContext context, AbstractFile file)
static synchronized IngestServices getInstance()