22 package org.sleuthkit.autopsy.coreutils;
 
   24 import com.google.common.collect.ImmutableSortedSet;
 
   25 import com.google.common.io.Files;
 
   26 import java.awt.Image;
 
   27 import java.awt.image.BufferedImage;
 
   28 import java.io.BufferedInputStream;
 
   30 import java.io.IOException;
 
   31 import java.io.InputStream;
 
   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.Iterator;
 
   39 import java.util.List;
 
   40 import static java.util.Objects.nonNull;
 
   41 import java.util.SortedSet;
 
   42 import java.util.TreeSet;
 
   43 import java.util.concurrent.ExecutionException;
 
   44 import java.util.concurrent.Executor;
 
   45 import java.util.concurrent.Executors;
 
   46 import java.util.logging.Level;
 
   47 import javafx.concurrent.Task;
 
   48 import javafx.embed.swing.SwingFXUtils;
 
   49 import javax.annotation.Nonnull;
 
   50 import javax.annotation.Nullable;
 
   51 import javax.imageio.IIOException;
 
   52 import javax.imageio.ImageIO;
 
   53 import javax.imageio.ImageReadParam;
 
   54 import javax.imageio.ImageReader;
 
   55 import javax.imageio.event.IIOReadProgressListener;
 
   56 import javax.imageio.stream.ImageInputStream;
 
   57 import org.apache.commons.lang3.ObjectUtils;
 
   58 import org.apache.commons.lang3.StringUtils;
 
   59 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 
   60 import org.opencv.core.Core;
 
   61 import org.openide.util.NbBundle;
 
   82     private static final String 
FORMAT = 
"png"; 
 
   91     private static final SortedSet<String> 
GIF_MIME_SET = ImmutableSortedSet.copyOf(
new String[]{
"image/gif"});
 
   99         ImageIO.scanForPlugins();
 
  100         BufferedImage tempImage;
 
  102             tempImage = ImageIO.read(
ImageUtils.class.getResourceAsStream(
"/org/sleuthkit/autopsy/images/file-icon.png"));
 
  103         } 
catch (IOException ex) {
 
  104             LOGGER.log(Level.SEVERE, 
"Failed to load default icon.", ex); 
 
  107         DEFAULT_THUMBNAIL = tempImage;
 
  110         boolean openCVLoadedTemp;
 
  112             System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 
  113             if (System.getProperty(
"os.arch").equals(
"amd64") || System.getProperty(
"os.arch").equals(
"x86_64")) { 
 
  114                 System.loadLibrary(
"opencv_ffmpeg248_64"); 
 
  116                 System.loadLibrary(
"opencv_ffmpeg248"); 
 
  119             openCVLoadedTemp = 
true;
 
  120         } 
catch (UnsatisfiedLinkError e) {
 
  121             openCVLoadedTemp = 
false;
 
  122             LOGGER.log(Level.SEVERE, 
"OpenCV Native code library failed to load", e); 
 
  127         openCVLoaded = openCVLoadedTemp;
 
  128         SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes()));
 
  129         SUPPORTED_IMAGE_EXTENSIONS.add(
"tec"); 
 
  130         SUPPORTED_IMAGE_MIME_TYPES = 
new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
 
  135         SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList(
 
  138                 "image/x-portable-graymap", 
 
  139                 "image/x-portable-bitmap", 
 
  140                 "application/x-123")); 
 
  141         SUPPORTED_IMAGE_MIME_TYPES.removeIf(
"application/octet-stream"::equals); 
 
  153             = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder()
 
  154                     .namingPattern(
"thumbnail-saver-%d").build()); 
 
  157         return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
 
  161         return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
 
  186         if (!(content instanceof AbstractFile)) {
 
  189         AbstractFile file = (AbstractFile) content;
 
  204         return isMediaThumbnailSupported(file, 
"image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || 
hasImageFileHeader(file);
 
  215     public static boolean isGIF(AbstractFile file) {
 
  216         return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
 
  239     static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix, 
final Collection<String> supportedMimeTypes, 
final List<String> supportedExtension) {
 
  240         if (
false == file.isFile() || file.getSize() <= 0) {
 
  244         String extension = file.getNameExtension();
 
  246         if (StringUtils.isNotBlank(extension) && supportedExtension.contains(extension)) {
 
  251                 if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
 
  254                 return supportedMimeTypes.contains(mimeType);
 
  255             } 
catch (FileTypeDetectorInitException | TskCoreException ex) {
 
  256                 LOGGER.log(Level.SEVERE, 
"Error determining MIME type of " + getContentPathSafe(file), ex);
 
  274         if (fileTypeDetector == null) {
 
  290     public static BufferedImage 
getThumbnail(Content content, 
int iconSize) {
 
  291         if (content instanceof AbstractFile) {
 
  292             AbstractFile file = (AbstractFile) content;
 
  297                 return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
 
  298             } 
catch (InterruptedException | ExecutionException ex) {
 
  299                 LOGGER.log(Level.WARNING, 
"Failed to get thumbnail for {0}: " + ex.toString(), getContentPathSafe(content)); 
 
  337             return Paths.get(cacheDirectory, 
"thumbnails", fileID + 
".png").toFile(); 
 
  338         } 
catch (IllegalStateException e) {
 
  339             LOGGER.log(Level.WARNING, 
"Could not get cached thumbnail location.  No case is open."); 
 
  364         if (file.getSize() < 100) {
 
  369             byte[] fileHeaderBuffer = 
readHeader(file, 2);
 
  374             return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
 
  375         } 
catch (TskCoreException ex) {
 
  391         byte[] fileHeaderBuffer;
 
  394             length = file.getSize();
 
  395             if (length % 2 != 0) {
 
  398             if (length >= 1024) {
 
  401             fileHeaderBuffer = 
readHeader(file, (
int) length); 
 
  402         } 
catch (TskCoreException ex) {
 
  407         if (fileHeaderBuffer != null) {
 
  408             for (
int index = 0; index < length; index += 2) {
 
  410                 if ((fileHeaderBuffer[index] == (byte) 0xFF) && (fileHeaderBuffer[index + 1] == (byte) 0xD8)) {
 
  428         if (file.getSize() < 10) {
 
  433             byte[] fileHeaderBuffer = 
readHeader(file, 8);
 
  438             return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E)
 
  439                     && ((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D)
 
  440                     && ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
 
  441                     && ((fileHeaderBuffer[7] & 0xff) == 0x0A));
 
  443         } 
catch (TskCoreException ex) {
 
  449     private static byte[] 
readHeader(AbstractFile file, 
int buffLength) 
throws TskCoreException {
 
  450         byte[] fileHeaderBuffer = 
new byte[buffLength];
 
  451         int bytesRead = file.read(fileHeaderBuffer, 0, buffLength);
 
  453         if (bytesRead != buffLength) {
 
  455             throw new TskCoreException(
"Could not read " + buffLength + 
" bytes from " + file.getName());
 
  457         return fileHeaderBuffer;
 
  472                 "ImageIO could not determine width of {0}: ", 
 
  473                 imageReader -> imageReader.getWidth(0)
 
  489                 "ImageIO could not determine height of {0}: ", 
 
  490                 imageReader -> imageReader.getHeight(0)
 
  505         public T 
extract(ImageReader reader) 
throws IOException;
 
  530         try (InputStream inputStream = 
new BufferedInputStream(
new ReadContentInputStream(file));) {
 
  531             try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
 
  533                     IIOException iioException = 
new IIOException(
"Could not create ImageInputStream.");
 
  534                     LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
 
  537                 Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
 
  539                 if (readers.hasNext()) {
 
  540                     ImageReader reader = readers.next();
 
  541                     reader.setInput(input);
 
  544                         return propertyExtractor.extract(reader);
 
  545                     } 
catch (IOException ex) {
 
  546                         LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
 
  552                     IIOException iioException = 
new IIOException(
"No ImageReader found.");
 
  553                     LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
 
  577     public static Task<javafx.scene.image.Image> 
newGetThumbnailTask(AbstractFile file, 
int iconSize, 
boolean defaultOnFailure) {
 
  592         @NbBundle.Messages({
"# {0} - file name",
 
  593             "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
 
  595             "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
 
  598             updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
 
  605         protected javafx.scene.image.Image 
call() throws Exception {
 
  613             if (cacheFile != null && cacheFile.exists()) {
 
  615                     BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
 
  616                     if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == 
iconSize) {
 
  617                         return SwingFXUtils.toFXImage(cachedThumbnail, null);
 
  619                 } 
catch (Exception ex) {
 
  620                     LOGGER.log(Level.WARNING, 
"ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  630             BufferedImage thumbnail = null;
 
  633                     updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
 
  634                     thumbnail = 
VideoUtils.generateVideoThumbnail(file, iconSize);
 
  636                 if (null == thumbnail) {
 
  637                     if (defaultOnFailure) {
 
  640                         throw new IIOException(
"Failed to generate a thumbnail for " + getContentPathSafe(file));
 
  647                 BufferedImage bufferedImage = SwingFXUtils.fromFXImage(
readImage(), null);
 
  648                 if (null == bufferedImage) {
 
  649                     String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
 
  650                     LOGGER.log(Level.WARNING, msg);
 
  651                     throw new IIOException(msg);
 
  653                 updateProgress(-1, 1);
 
  658                 } 
catch (IllegalArgumentException | OutOfMemoryError e) {
 
  660                     LOGGER.log(Level.WARNING, 
"Cropping {0}, because it could not be scaled: " + e.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  662                     final int height = bufferedImage.getHeight();
 
  663                     final int width = bufferedImage.getWidth();
 
  664                     if (iconSize < height || iconSize < width) {
 
  665                         final int cropHeight = Math.min(iconSize, height);
 
  666                         final int cropWidth = Math.min(iconSize, width);
 
  669                         } 
catch (Exception cropException) {
 
  670                             LOGGER.log(Level.WARNING, 
"Could not crop {0}: " + cropException.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  673                 } 
catch (Exception e) {
 
  674                     LOGGER.log(Level.WARNING, 
"Could not scale {0}: " + e.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  683             updateProgress(-1, 1);
 
  686             if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
 
  690             return SwingFXUtils.toFXImage(thumbnail, null);
 
  701                     Files.createParentDirs(cacheFile);
 
  702                     if (cacheFile.exists()) {
 
  705                     ImageIO.write(thumbnail, FORMAT, cacheFile);
 
  706                 } 
catch (IllegalArgumentException | IOException ex) {
 
  707                     LOGGER.log(Level.WARNING, 
"Could not write thumbnail for {0}: " + ex.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  735         "ReadImageTask.mesageText=Reading image: {0}"})
 
  740             updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
 
  744         protected javafx.scene.image.Image 
call() throws Exception {
 
  752     static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
 
  755         final AbstractFile file;
 
  762         protected javafx.scene.image.Image 
readImage() throws IOException {
 
  765                 javafx.scene.image.Image image = 
new javafx.scene.image.Image(
new BufferedInputStream(
new ReadContentInputStream(file)));
 
  766                 if (image.isError() == 
false) {
 
  769             } 
else if (file.getNameExtension().equalsIgnoreCase(
"tec")) { 
 
  770                 ReadContentInputStream readContentInputStream = 
new ReadContentInputStream(file);
 
  774                 javafx.scene.image.Image image = 
new javafx.scene.image.Image(
new BufferedInputStream(readContentInputStream));
 
  775                 if (image.isError() == 
false) {
 
  793                         ImageReadParam param = imageReader.getDefaultReadParam();
 
  794                         BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
 
  795                         param.setDestination(bufferedImage);
 
  797                             bufferedImage = imageReader.read(0, param); 
 
  798                         } 
catch (IOException iOException) {
 
  799                             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + iOException.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  806                         return SwingFXUtils.toFXImage(bufferedImage, null);
 
  814             updateProgress(percentageDone, 100);
 
  816                 reader.removeIIOReadProgressListener(
this);
 
  826                 javafx.scene.image.Image fxImage = 
get();
 
  827                 if (fxImage == null) {
 
  828                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, 
ImageUtils.getContentPathSafe(file));
 
  829                 } 
else if (fxImage.isError()) {
 
  831                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + ObjectUtils.toString(fxImage.getException()), 
ImageUtils.getContentPathSafe(file));
 
  833             } 
catch (InterruptedException | ExecutionException ex) {
 
  841             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + ObjectUtils.toString(getException()), 
ImageUtils.getContentPathSafe(file));
 
  846             updateProgress(100, 100);
 
  886     static String getContentPathSafe(Content content) {
 
  888             return content.getUniquePath();
 
  889         } 
catch (TskCoreException tskCoreException) {
 
  890             String contentName = content.getName();
 
  891             LOGGER.log(Level.SEVERE, 
"Failed to get unique path for " + contentName, tskCoreException); 
 
  940     public static BufferedImage 
getIcon(Content content, 
int iconSize) {
 
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)
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 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 final boolean openCVLoaded
static Image getDefaultIcon()
GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
static int getImageHeight(AbstractFile file)
void thumbnailProgress(ImageReader source, float percentageDone)
static List< String > getSupportedImageExtensions()
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)
String detect(AbstractFile file)
static File getCachedThumbnailLocation(long fileID)
static Case getCurrentCase()
synchronized static Logger getLogger(String name)
static int getImageWidth(AbstractFile file)
static< T > T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor< T > propertyExtractor)
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()