22package org.sleuthkit.autopsy.coreutils;
24import com.google.common.collect.ImmutableSortedSet;
26import java.awt.image.BufferedImage;
27import java.io.BufferedInputStream;
29import java.io.IOException;
30import java.io.InputStream;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.text.MessageFormat;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.EnumSet;
39import java.util.Iterator;
41import java.util.Objects;
42import static java.util.Objects.nonNull;
43import java.util.SortedSet;
44import java.util.TreeSet;
45import java.util.concurrent.ConcurrentHashMap;
46import java.util.concurrent.ExecutionException;
47import java.util.concurrent.Executor;
48import java.util.concurrent.Executors;
49import java.util.logging.Level;
50import java.util.stream.Collectors;
51import java.util.stream.Stream;
52import javafx.concurrent.Task;
53import javafx.embed.swing.SwingFXUtils;
54import javax.annotation.Nonnull;
55import javax.annotation.Nullable;
56import javax.imageio.IIOException;
57import javax.imageio.ImageIO;
58import javax.imageio.ImageReadParam;
59import javax.imageio.ImageReader;
60import javax.imageio.event.IIOReadProgressListener;
61import javax.imageio.stream.ImageInputStream;
62import org.apache.commons.lang3.StringUtils;
63import org.apache.commons.lang3.concurrent.BasicThreadFactory;
64import org.openide.util.NbBundle;
65import org.openide.util.NbBundle.Messages;
66import org.sleuthkit.autopsy.casemodule.Case;
67import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
68import org.sleuthkit.autopsy.corelibs.OpenCvLoader;
69import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
70import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
71import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException;
72import org.sleuthkit.datamodel.AbstractFile;
73import org.sleuthkit.datamodel.Content;
74import org.sleuthkit.datamodel.ReadContentInputStream;
75import org.sleuthkit.datamodel.TskCoreException;
88 private static final String
FORMAT =
"png";
97 private static final SortedSet<String>
GIF_MIME_SET = ImmutableSortedSet.copyOf(
new String[]{
"image/gif"});
111 @Messages({
"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg",
112 "ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"})
113 private static final ConcurrentHashMap<Long, File>
cacheFileMap =
new ConcurrentHashMap<>();
116 ImageIO.scanForPlugins();
117 BufferedImage tempImage;
119 tempImage = ImageIO.read(
ImageUtils.class.getResourceAsStream(
"/org/sleuthkit/autopsy/images/file-icon.png"));
120 }
catch (IOException ex) {
121 LOGGER.log(Level.SEVERE,
"Failed to load default icon.", ex);
125 boolean tempFfmpegLoaded =
false;
128 if (System.getProperty(
"os.arch").equals(
"amd64") || System.getProperty(
"os.arch").equals(
"x86_64")) {
129 System.loadLibrary(
"opencv_ffmpeg2413_64");
131 System.loadLibrary(
"opencv_ffmpeg2413");
133 tempFfmpegLoaded =
true;
134 }
catch (UnsatisfiedLinkError e) {
135 tempFfmpegLoaded =
false;
136 LOGGER.log(Level.SEVERE, Bundle.ImageUtils_ffmpegLoadedError_msg(), e);
145 List<String> imageSuffixList = Arrays.stream(ImageIO.getReaderFileSuffixes())
146 .filter((extension) -> StringUtils.isNotBlank(extension))
147 .collect(Collectors.toList());
153 List<String> mimeTypeList = Stream.of(ImageIO.getReaderMIMETypes())
157 .filter((mimeType) -> StringUtils.isNotBlank(mimeType))
158 .collect(Collectors.toList());
168 "image/x-portable-graymap",
169 "image/x-portable-bitmap",
171 "application/x-123"));
187 = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder()
188 .namingPattern(
"thumbnail-saver-%d").build());
220 if (!(content instanceof AbstractFile)) {
223 AbstractFile file = (AbstractFile) content;
233 if (isSupportedMediaExtension(file, supportedExtensions)) {
261 public static boolean isGIF(AbstractFile file) {
285 static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix,
final Collection<String> supportedMimeTypes,
final List<String> supportedExtension) {
286 if (
false == file.isFile() || file.getSize() <= 0) {
290 if (isSupportedMediaExtension(file, supportedExtension)) {
295 if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
298 return supportedMimeTypes.contains(mimeType);
299 }
catch (FileTypeDetectorInitException ex) {
300 LOGGER.log(Level.SEVERE,
"Error determining MIME type of " + getContentPathSafe(file), ex);
315 static boolean isSupportedMediaExtension(
final AbstractFile file,
final List<String> supportedExtensions) {
316 String extension = file.getNameExtension();
318 return (StringUtils.isNotBlank(extension) && supportedExtensions.contains(extension));
349 public static BufferedImage
getThumbnail(Content content,
int iconSize) {
350 if (content instanceof AbstractFile) {
351 AbstractFile file = (AbstractFile) content;
358 if (Thread.interrupted()) {
361 final BufferedImage image = ImageIO.read(bufferedReadContentStream);
363 if (Thread.interrupted()) {
368 }
catch (IOException iOException) {
369 LOGGER.log(Level.WARNING,
"Failed to get thumbnail for " + getContentPathSafe(content), iOException);
375 if (Thread.interrupted()) {
380 return SwingFXUtils.fromFXImage(thumbnailTask.get(),
null);
381 }
catch (InterruptedException | ExecutionException ex) {
382 LOGGER.log(Level.WARNING,
"Failed to get thumbnail for " + getContentPathSafe(content), ex);
398 return new BufferedInputStream(
new ReadContentInputStream(file));
431 return Paths.get(cacheDirectory,
"thumbnails", fileID +
".png").toFile();
433 LOGGER.log(Level.INFO,
"Could not get cached thumbnail location. No case is open.");
459 if (file.getSize() < 100) {
464 byte[] fileHeaderBuffer =
readHeader(file, 2);
469 return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
470 }
catch (TskCoreException ex) {
486 byte[] fileHeaderBuffer;
489 length = file.getSize();
490 if (length % 2 != 0) {
493 if (length >= 1024) {
496 fileHeaderBuffer =
readHeader(file, (
int) length);
497 }
catch (TskCoreException ex) {
502 if (fileHeaderBuffer !=
null) {
503 for (
int index = 0; index < length; index += 2) {
505 if ((fileHeaderBuffer[index] == (
byte) 0xFF) && (fileHeaderBuffer[index + 1] == (
byte) 0xD8)) {
523 if (file.getSize() < 10) {
528 byte[] fileHeaderBuffer =
readHeader(file, 8);
533 return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E)
534 && ((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D)
535 && ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
536 && ((fileHeaderBuffer[7] & 0xff) == 0x0A));
538 }
catch (TskCoreException ex) {
544 private static byte[]
readHeader(AbstractFile file,
int buffLength)
throws TskCoreException {
545 byte[] fileHeaderBuffer =
new byte[buffLength];
546 int bytesRead = file.read(fileHeaderBuffer, 0, buffLength);
548 if (bytesRead != buffLength) {
550 throw new TskCoreException(
"Could not read " + buffLength +
" bytes from " + file.getName());
552 return fileHeaderBuffer;
567 "ImageIO could not determine width of {0}: ",
568 imageReader -> imageReader.getWidth(0)
584 "ImageIO could not determine height of {0}: ",
585 imageReader -> imageReader.getHeight(0)
601 public T
extract(ImageReader reader)
throws IOException;
627 ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
629 IIOException iioException =
new IIOException(
"Could not create ImageInputStream.");
630 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
633 Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
635 if (readers.hasNext()) {
636 ImageReader reader = readers.next();
637 reader.setInput(input);
639 return propertyExtractor.extract(reader);
640 }
catch (IOException ex) {
641 LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
647 IIOException iioException =
new IIOException(
"No ImageReader found.");
648 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
670 public static Task<javafx.scene.image.Image>
newGetThumbnailTask(AbstractFile file,
int iconSize,
boolean defaultOnFailure) {
686 @NbBundle.Messages({
"# {0} - file name",
687 "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
689 "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
692 updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
699 protected javafx.scene.image.Image
call() throws Exception {
715 BufferedImage cachedThumbnail = ImageIO.read(
cacheFile);
719 if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() ==
iconSize) {
720 return SwingFXUtils.toFXImage(cachedThumbnail,
null);
722 }
catch (Exception ex) {
723 LOGGER.log(Level.WARNING,
"ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(),
ImageUtils.getContentPathSafe(file));
731 BufferedImage thumbnail =
null;
734 updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
740 if (
null == thumbnail) {
744 throw new IIOException(
"Failed to generate a thumbnail for " + getContentPathSafe(file));
754 BufferedImage bufferedImage = SwingFXUtils.fromFXImage(
readImage(),
null);
755 if (
null == bufferedImage) {
757 LOGGER.log(Level.WARNING, msg);
758 throw new IIOException(msg);
760 updateProgress(-1, 1);
770 }
catch (IllegalArgumentException | OutOfMemoryError e) {
772 LOGGER.log(Level.WARNING,
"Cropping {0}, because it could not be scaled: " + e.toString(),
ImageUtils.getContentPathSafe(file));
774 final int height = bufferedImage.getHeight();
775 final int width = bufferedImage.getWidth();
777 final int cropHeight = Math.min(
iconSize, height);
778 final int cropWidth = Math.min(
iconSize, width);
787 }
catch (Exception cropException) {
788 LOGGER.log(Level.WARNING,
"Could not crop {0}: " + cropException.toString(),
ImageUtils.getContentPathSafe(file));
791 }
catch (Exception e) {
792 LOGGER.log(Level.WARNING,
"Could not scale {0}: " + e.toString(),
ImageUtils.getContentPathSafe(file));
797 updateProgress(-1, 1);
806 return SwingFXUtils.toFXImage(thumbnail,
null);
818 Path path = Paths.get(
cacheFile.getParent());
819 File thumbsDir = Paths.get(
cacheFile.getParent()).toFile();
820 if (!thumbsDir.exists()) {
829 }
catch (Exception ex) {
830 LOGGER.log(Level.WARNING,
"Could not write thumbnail for {0}: " + ex.toString(),
ImageUtils.getContentPathSafe(file));
859 "ReadImageTask.mesageText=Reading image: {0}"})
860 static private class ReadImageTask extends ReadImageTaskBase {
862 ReadImageTask(AbstractFile file) {
864 updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
868 protected javafx.scene.image.Image
call() throws Exception {
876 static private abstract class ReadImageTaskBase
extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
879 final AbstractFile file;
881 ReadImageTaskBase(AbstractFile file) {
885 protected javafx.scene.image.Image
readImage() throws IOException {
892 if (image.isError() ==
false) {
895 }
else if (file.getNameExtension().equalsIgnoreCase(
"tec")) {
896 ReadContentInputStream readContentInputStream =
new ReadContentInputStream(file);
900 javafx.scene.image.Image image =
new javafx.scene.image.Image(
new BufferedInputStream(readContentInputStream));
901 if (image.isError() ==
false) {
908 imageReader.addIIOReadProgressListener(ReadImageTaskBase.this);
915 ImageReadParam param = imageReader.getDefaultReadParam();
916 BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
917 param.setDestination(bufferedImage);
922 bufferedImage = imageReader.read(0, param);
923 }
catch (IOException iOException) {
926 imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this);
931 return SwingFXUtils.toFXImage(bufferedImage,
null);
939 updateProgress(percentageDone, 100);
941 reader.removeIIOReadProgressListener(
this);
949 if (Thread.interrupted()) {
953 return super.isCancelled();
960 javafx.scene.image.Image fxImage =
get();
961 if (fxImage ==
null) {
963 }
else if (fxImage.isError()) {
967 }
catch (InterruptedException | ExecutionException ex) {
980 updateProgress(100, 100);
1020 static String getContentPathSafe(Content content) {
1022 return content.getUniquePath();
1023 }
catch (TskCoreException tskCoreException) {
1024 String contentName = content.getName();
1025 LOGGER.log(Level.SEVERE,
"Failed to get unique path for " + contentName, tskCoreException);
1074 public static BufferedImage
getIcon(Content content,
int iconSize) {
static Case getCurrentCaseThrows()
String getCacheDirectory()
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
static boolean openCvIsLoaded()
static synchronized BufferedImage resizeFast(BufferedImage input, int size)
static synchronized BufferedImage cropImage(BufferedImage input, int width, int height)
static synchronized BufferedImage resizeHighQuality(BufferedImage input, int width, int height)
void saveThumbnail(BufferedImage thumbnail)
javafx.scene.image.Image call()
GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
final boolean defaultOnFailure
static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION
void sequenceComplete(ImageReader source)
void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex)
void readAborted(ImageReader source)
void imageStarted(ImageReader source, int imageIndex)
static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT
void thumbnailComplete(ImageReader source)
void sequenceStarted(ImageReader source, int minIndex)
void imageComplete(ImageReader source)
void imageProgress(ImageReader reader, float percentageDone)
void thumbnailProgress(ImageReader source, float percentageDone)
javafx.scene.image.Image readImage()
javafx.scene.image.Image call()
static final List< String > SUPPORTED_IMAGE_EXTENSIONS
static Image getDefaultThumbnail()
static List< String > getSupportedImageExtensions()
static final int ICON_SIZE_SMALL
static Task< javafx.scene.image.Image > newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
static final SortedSet< String > GIF_MIME_SET
static File getIconFile(Content content, int iconSize)
static File getFile(long id)
static boolean hasImageFileHeader(AbstractFile file)
static BufferedImage getIcon(Content content, int iconSize)
static File getCachedThumbnailLocation(long fileID)
static boolean thumbnailSupported(Content content)
static long getJfifStartOfImageOffset(AbstractFile file)
static int getImageWidth(AbstractFile file)
static final SortedSet< String > SUPPORTED_IMAGE_MIME_TYPES
static final Logger LOGGER
static BufferedInputStream getBufferedReadContentStream(AbstractFile file)
static final String FORMAT
static final Executor imageSaver
static boolean isGIF(AbstractFile file)
static File getCachedThumbnailFile(Content content, int iconSize)
static boolean isImageThumbnailSupported(AbstractFile file)
static BufferedImage getThumbnail(Content content, int iconSize)
static byte[] readHeader(AbstractFile file, int buffLength)
static final int ICON_SIZE_LARGE
synchronized static FileTypeDetector getFileTypeDetector()
static int getImageHeight(AbstractFile file)
static boolean isJpegFileHeader(AbstractFile file)
static final ConcurrentHashMap< Long, File > cacheFileMap
static final List< String > GIF_EXTENSION_LIST
static FileTypeDetector fileTypeDetector
static final boolean FFMPEG_LOADED
static boolean isPngFileHeader(AbstractFile file)
static< T > T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor< T > propertyExtractor)
static Image getDefaultIcon()
static final BufferedImage DEFAULT_THUMBNAIL
static Task< javafx.scene.image.Image > newReadImageTask(AbstractFile file)
static SortedSet< String > getSupportedImageMimeTypes()
static final int ICON_SIZE_MEDIUM
synchronized static Logger getLogger(String name)
static void show(String title, String message, MessageType type, ActionListener actionListener)
static boolean isVideoThumbnailSupported(AbstractFile file)
static List< String > getSupportedVideoExtensions()
String getMIMEType(AbstractFile file)