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;
68 @ServiceProvider(service = PictureProcessor.class)
73 private static final int EXIT_SUCCESS = 0;
74 private static final String HEIC_MODULE_FOLDER =
"HEIC";
75 private static final long TIMEOUT_IN_SEC = TimeUnit.SECONDS.convert(2, TimeUnit.MINUTES);
78 private static final String IMAGE_MAGICK_FOLDER =
"ImageMagick-7.0.10-27-portable-Q16-x64";
79 private static final String IMAGE_MAGICK_EXE =
"magick.exe";
80 private static final String IMAGE_MAGICK_ERROR_FILE =
"magick_error.txt";
86 IMAGE_MAGICK_PATH = findImageMagick();
88 if (IMAGE_MAGICK_PATH == null) {
89 logger.log(Level.WARNING,
"ImageMagick executable not found. "
90 +
"HEIC functionality will be automatically disabled.");
95 final Path windowsLocation = Paths.get(IMAGE_MAGICK_FOLDER, IMAGE_MAGICK_EXE);
96 final Path macAndLinuxLocation = Paths.get(
"/usr",
"local",
"bin",
"magick");
101 final File locatedExec = InstalledFileLocator.getDefault().locate(
102 windowsLocation.toString(),
HEICProcessor.class.getPackage().getName(),
false);
104 return (locatedExec != null) ? locatedExec.toPath() : null;
105 }
else if ((osName.equals(
"linux") || osName.startsWith(
"mac")) &&
106 Files.isExecutable(macAndLinuxLocation) &&
107 !Files.isDirectory(macAndLinuxLocation)) {
108 return macAndLinuxLocation;
121 return Paths.get(moduleOutputDirectory,
123 String.valueOf(file.getId()));
130 final Path moduleOutputFolder = getModuleOutputFolder(file);
132 if (!Files.exists(moduleOutputFolder)) {
133 Files.createDirectories(moduleOutputFolder);
140 if (IMAGE_MAGICK_PATH == null) {
143 createModuleOutputFolder(file);
149 final Path localDiskCopy = extractToDisk(file);
151 convertToJPEG(context, localDiskCopy, file);
152 }
catch (IOException ex) {
153 logger.log(Level.WARNING,
"I/O error encountered during HEIC photo processing.", ex);
154 }
catch (TskCoreException ex) {
155 logger.log(Level.SEVERE,
"Unable to add pictures as derived files.", ex);
157 logger.log(Level.WARNING,
"No open case!", ex);
168 final Path localDiskCopy = Paths.get(tempDir, heicFileName);
170 try (BufferedInputStream heicInputStream =
new BufferedInputStream(
new ReadContentInputStream(heicFile))) {
171 Files.copy(heicInputStream, localDiskCopy, StandardCopyOption.REPLACE_EXISTING);
172 return localDiskCopy;
180 final Path moduleOutputFolder = getModuleOutputFolder(heicFile);
183 final Path outputFile = moduleOutputFolder.resolve(baseFileName +
".jpg");
185 final Path imageMagickErrorOutput = moduleOutputFolder.resolve(IMAGE_MAGICK_ERROR_FILE);
186 Files.deleteIfExists(imageMagickErrorOutput);
187 Files.createFile(imageMagickErrorOutput);
192 final ProcessBuilder processBuilder =
new ProcessBuilder()
193 .command(IMAGE_MAGICK_PATH.toString(),
194 localDiskCopy.toString(),
195 outputFile.toString());
197 processBuilder.redirectError(imageMagickErrorOutput.toFile());
201 if (context.fileIngestIsCancelled()) {
205 if (exitStatus != EXIT_SUCCESS) {
206 logger.log(Level.INFO,
"Non-zero exit status for HEIC file [id: {0}]. Skipping...", heicFile.getId());
212 final String glob = String.format(
"{%1$s.jpg,%1$s-*.jpg}", baseFileName);
213 try (DirectoryStream<Path> stream = Files.newDirectoryStream(moduleOutputFolder, glob)) {
216 for (Path candidate : stream) {
217 if (context.fileIngestIsCancelled()) {
221 final BasicFileAttributes attrs = Files.readAttributes(candidate, BasicFileAttributes.class);
222 final Path localCasePath = caseDirectory.relativize(candidate);
225 .addDerivedFile(candidate.getFileName().toString(),
226 localCasePath.toString(), attrs.size(), 0L,
227 attrs.creationTime().to(TimeUnit.SECONDS),
228 attrs.lastAccessTime().to(TimeUnit.SECONDS),
229 attrs.lastModifiedTime().to(TimeUnit.SECONDS),
230 attrs.isRegularFile(), heicFile,
"",
231 "",
"",
"", TskData.EncodingType.NONE);
233 context.addFilesToJob(Arrays.asList(jpegFile));
237 }
catch (DirectoryIteratorException ex) {
244 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()