22 package org.sleuthkit.autopsy.coreutils;
 
   24 import com.google.common.collect.ImmutableSortedSet;
 
   25 import java.awt.Image;
 
   26 import java.awt.image.BufferedImage;
 
   27 import java.io.BufferedInputStream;
 
   29 import java.io.IOException;
 
   30 import java.io.InputStream;
 
   31 import java.nio.file.Path;
 
   32 import java.nio.file.Paths;
 
   33 import java.text.MessageFormat;
 
   34 import java.util.ArrayList;
 
   35 import java.util.Arrays;
 
   36 import java.util.Collection;
 
   37 import java.util.Collections;
 
   38 import java.util.EnumSet;
 
   39 import java.util.Iterator;
 
   40 import java.util.List;
 
   41 import java.util.Objects;
 
   42 import static java.util.Objects.nonNull;
 
   43 import java.util.SortedSet;
 
   44 import java.util.TreeSet;
 
   45 import java.util.concurrent.ConcurrentHashMap;
 
   46 import java.util.concurrent.ExecutionException;
 
   47 import java.util.concurrent.Executor;
 
   48 import java.util.concurrent.Executors;
 
   49 import java.util.logging.Level;
 
   50 import java.util.stream.Collectors;
 
   51 import java.util.stream.Stream;
 
   52 import javafx.concurrent.Task;
 
   53 import javafx.embed.swing.SwingFXUtils;
 
   54 import javax.annotation.Nonnull;
 
   55 import javax.annotation.Nullable;
 
   56 import javax.imageio.IIOException;
 
   57 import javax.imageio.ImageIO;
 
   58 import javax.imageio.ImageReadParam;
 
   59 import javax.imageio.ImageReader;
 
   60 import javax.imageio.event.IIOReadProgressListener;
 
   61 import javax.imageio.stream.ImageInputStream;
 
   62 import org.apache.commons.lang3.StringUtils;
 
   63 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 
   64 import org.openide.util.NbBundle;
 
   65 import org.openide.util.NbBundle.Messages;
 
   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); 
 
  124         DEFAULT_THUMBNAIL = tempImage;
 
  125         boolean tempFfmpegLoaded = 
false;
 
  128                 if (System.getProperty(
"os.arch").equals(
"amd64") || System.getProperty(
"os.arch").equals(
"x86_64")) { 
 
  129                     System.loadLibrary(
"opencv_ffmpeg248_64"); 
 
  131                     System.loadLibrary(
"opencv_ffmpeg248"); 
 
  133                 tempFfmpegLoaded = 
true;
 
  134             } 
catch (UnsatisfiedLinkError e) {
 
  135                 tempFfmpegLoaded = 
false;
 
  136                 LOGGER.log(Level.SEVERE, Bundle.ImageUtils_ffmpegLoadedError_msg(), e); 
 
  140         FFMPEG_LOADED = tempFfmpegLoaded;
 
  145         List<String> imageSuffixList = Arrays.stream(ImageIO.getReaderFileSuffixes())
 
  146             .filter((extension) -> StringUtils.isNotBlank(extension))
 
  147             .collect(Collectors.toList());
 
  149         SUPPORTED_IMAGE_EXTENSIONS.addAll(imageSuffixList);
 
  150         SUPPORTED_IMAGE_EXTENSIONS.add(
"tec"); 
 
  151         SUPPORTED_IMAGE_EXTENSIONS.removeIf(
"db"::equals); 
 
  153         List<String> mimeTypeList = Stream.of(ImageIO.getReaderMIMETypes())
 
  157                 .filter((mimeType) -> StringUtils.isNotBlank(mimeType))
 
  158                 .collect(Collectors.toList());
 
  160         SUPPORTED_IMAGE_MIME_TYPES = 
new TreeSet<>(mimeTypeList);
 
  165         SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList(
 
  168                 "image/x-portable-graymap", 
 
  169                 "image/x-portable-bitmap", 
 
  171                 "application/x-123")); 
 
  172         SUPPORTED_IMAGE_MIME_TYPES.removeIf(
"application/octet-stream"::equals); 
 
  187             = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder()
 
  188                     .namingPattern(
"thumbnail-saver-%d").build()); 
 
  191         return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
 
  195         return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
 
  220         if (!(content instanceof AbstractFile)) {
 
  223         AbstractFile file = (AbstractFile) content;
 
  233         if (isSupportedMediaExtension(file, supportedExtensions)) {
 
  250         return isMediaThumbnailSupported(file, 
"image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || 
hasImageFileHeader(file);
 
  261     public static boolean isGIF(AbstractFile file) {
 
  262         return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
 
  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));
 
  333         if (fileTypeDetector == null) {
 
  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 {
 
  708             if (cacheFile != null) {
 
  710                     if (cacheFile.exists()) {
 
  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()));
 
  738                     thumbnail = 
VideoUtils.generateVideoThumbnail(file, iconSize);
 
  740                 if (null == thumbnail) {
 
  741                     if (defaultOnFailure) {
 
  744                         throw new IIOException(
"Failed to generate a thumbnail for " + getContentPathSafe(file));
 
  754                 BufferedImage bufferedImage = SwingFXUtils.fromFXImage(
readImage(), null);
 
  755                 if (null == bufferedImage) {
 
  756                     String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
 
  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();
 
  776                     if (iconSize < height || iconSize < width) {
 
  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);
 
  800             if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
 
  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()) {
 
  824                         if (cacheFile.exists()) {
 
  827                         ImageIO.write(thumbnail, FORMAT, cacheFile);
 
  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}"})
 
  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;
 
  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) {
 
  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) {
 
  924                             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + iOException.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  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) {
 
  962                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, 
ImageUtils.getContentPathSafe(file));
 
  963                 } 
else if (fxImage.isError()) {
 
  965                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + Objects.toString(fxImage.getException()), 
ImageUtils.getContentPathSafe(file));
 
  967             } 
catch (InterruptedException | ExecutionException ex) {
 
  975             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + Objects.toString(getException()), 
ImageUtils.getContentPathSafe(file));
 
  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 List< String > getSupportedVideoExtensions()
static File getIconFile(Content content, int iconSize)
static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT
static boolean isGIF(AbstractFile file)
static final List< String > SUPPORTED_IMAGE_EXTENSIONS
javafx.scene.image.Image call()
static boolean isPngFileHeader(AbstractFile file)
static boolean thumbnailSupported(Content content)
static File getFile(long id)
void sequenceComplete(ImageReader source)
static final Logger LOGGER
void imageProgress(ImageReader reader, float percentageDone)
void readAborted(ImageReader source)
static Task< javafx.scene.image.Image > newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
final boolean defaultOnFailure
void imageStarted(ImageReader source, int imageIndex)
static final int ICON_SIZE_SMALL
static synchronized BufferedImage resizeFast(BufferedImage input, int size)
String getMIMEType(AbstractFile file)
static FileTypeDetector fileTypeDetector
static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION
static final SortedSet< String > GIF_MIME_SET
static Task< javafx.scene.image.Image > newReadImageTask(AbstractFile file)
void sequenceStarted(ImageReader source, int minIndex)
static synchronized BufferedImage resizeHighQuality(BufferedImage input, int width, int height)
static boolean isJpegFileHeader(AbstractFile file)
javafx.scene.image.Image readImage()
static final Executor imageSaver
void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex)
static final int ICON_SIZE_MEDIUM
static Image getDefaultIcon()
GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
static boolean openCvIsLoaded()
static int getImageHeight(AbstractFile file)
void thumbnailProgress(ImageReader source, float percentageDone)
static List< String > getSupportedImageExtensions()
static final boolean FFMPEG_LOADED
String getCacheDirectory()
void thumbnailComplete(ImageReader source)
static Image getDefaultThumbnail()
static final BufferedImage DEFAULT_THUMBNAIL
static long getJfifStartOfImageOffset(AbstractFile file)
javafx.scene.image.Image call()
static File getCachedThumbnailFile(Content content, int iconSize)
static boolean hasImageFileHeader(AbstractFile file)
static byte[] readHeader(AbstractFile file, int buffLength)
static File getCachedThumbnailLocation(long fileID)
static final ConcurrentHashMap< Long, File > cacheFileMap
synchronized static Logger getLogger(String name)
static int getImageWidth(AbstractFile file)
static void show(String title, String message, MessageType type, ActionListener actionListener)
static< T > T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor< T > propertyExtractor)
static Case getCurrentCaseThrows()
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
static BufferedInputStream getBufferedReadContentStream(AbstractFile file)
static boolean isImageThumbnailSupported(AbstractFile file)
static BufferedImage getThumbnail(Content content, int iconSize)
static final int ICON_SIZE_LARGE
static final List< String > GIF_EXTENSION_LIST
static final String FORMAT
synchronized static FileTypeDetector getFileTypeDetector()
void imageComplete(ImageReader source)
void saveThumbnail(BufferedImage thumbnail)
static BufferedImage getIcon(Content content, int iconSize)
static boolean isVideoThumbnailSupported(AbstractFile file)
static final SortedSet< String > SUPPORTED_IMAGE_MIME_TYPES
static synchronized BufferedImage cropImage(BufferedImage input, int width, int height)
static SortedSet< String > getSupportedImageMimeTypes()