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.ConcurrentHashMap;
 
   44 import java.util.concurrent.ExecutionException;
 
   45 import java.util.concurrent.Executor;
 
   46 import java.util.concurrent.Executors;
 
   47 import java.util.logging.Level;
 
   48 import javafx.concurrent.Task;
 
   49 import javafx.embed.swing.SwingFXUtils;
 
   50 import javax.annotation.Nonnull;
 
   51 import javax.annotation.Nullable;
 
   52 import javax.imageio.IIOException;
 
   53 import javax.imageio.ImageIO;
 
   54 import javax.imageio.ImageReadParam;
 
   55 import javax.imageio.ImageReader;
 
   56 import javax.imageio.event.IIOReadProgressListener;
 
   57 import javax.imageio.stream.ImageInputStream;
 
   58 import org.apache.commons.lang3.ObjectUtils;
 
   59 import org.apache.commons.lang3.StringUtils;
 
   60 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 
   61 import org.opencv.core.Core;
 
   62 import org.openide.util.NbBundle;
 
   83     private static final String 
FORMAT = 
"png"; 
 
   92     private static final SortedSet<String> 
GIF_MIME_SET = ImmutableSortedSet.copyOf(
new String[]{
"image/gif"});
 
  106     private static final ConcurrentHashMap<Long, File> 
cacheFileMap = 
new ConcurrentHashMap<>();
 
  109         ImageIO.scanForPlugins();
 
  110         BufferedImage tempImage;
 
  112             tempImage = ImageIO.read(
ImageUtils.class.getResourceAsStream(
"/org/sleuthkit/autopsy/images/file-icon.png"));
 
  113         } 
catch (IOException ex) {
 
  114             LOGGER.log(Level.SEVERE, 
"Failed to load default icon.", ex); 
 
  117         DEFAULT_THUMBNAIL = tempImage;
 
  120         boolean openCVLoadedTemp;
 
  122             System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 
  123             if (System.getProperty(
"os.arch").equals(
"amd64") || System.getProperty(
"os.arch").equals(
"x86_64")) { 
 
  124                 System.loadLibrary(
"opencv_ffmpeg248_64"); 
 
  126                 System.loadLibrary(
"opencv_ffmpeg248"); 
 
  129             openCVLoadedTemp = 
true;
 
  130         } 
catch (UnsatisfiedLinkError e) {
 
  131             openCVLoadedTemp = 
false;
 
  132             LOGGER.log(Level.SEVERE, 
"OpenCV Native code library failed to load", e); 
 
  137         OPEN_CV_LOADED = openCVLoadedTemp;
 
  138         SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes()));
 
  139         SUPPORTED_IMAGE_EXTENSIONS.add(
"tec"); 
 
  140         SUPPORTED_IMAGE_EXTENSIONS.removeIf(
"db"::equals); 
 
  142         SUPPORTED_IMAGE_MIME_TYPES = 
new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
 
  147         SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList(
 
  150                 "image/x-portable-graymap", 
 
  151                 "image/x-portable-bitmap", 
 
  152                 "application/x-123")); 
 
  153         SUPPORTED_IMAGE_MIME_TYPES.removeIf(
"application/octet-stream"::equals); 
 
  168             = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder()
 
  169                     .namingPattern(
"thumbnail-saver-%d").build()); 
 
  172         return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
 
  176         return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
 
  204         AbstractFile file = (AbstractFile) content;
 
  219         return isMediaThumbnailSupported(file, 
"image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || 
hasImageFileHeader(file);
 
  231         return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
 
  254     static boolean isMediaThumbnailSupported(
AbstractFile file, String mimeTypePrefix, 
final Collection<String> supportedMimeTypes, 
final List<String> supportedExtension) {
 
  261         if (StringUtils.isNotBlank(extension) && supportedExtension.contains(extension)) {
 
  266                 if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
 
  269                 return supportedMimeTypes.contains(mimeType);
 
  270             } 
catch (FileTypeDetectorInitException | TskCoreException ex) {
 
  271                 LOGGER.log(Level.SEVERE, 
"Error determining MIME type of " + getContentPathSafe(file), ex);
 
  289         if (fileTypeDetector == null) {
 
  307             AbstractFile file = (AbstractFile) content;
 
  314                     final BufferedImage image = ImageIO.
read(bufferedReadContentStream);
 
  318                 } 
catch (IOException iOException) {
 
  319                     LOGGER.log(Level.WARNING, 
"Failed to get thumbnail for " + getContentPathSafe(content), iOException); 
 
  327                 return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
 
  328             } 
catch (InterruptedException | ExecutionException ex) {
 
  329                 LOGGER.log(Level.WARNING, 
"Failed to get thumbnail for " + getContentPathSafe(content), ex); 
 
  375         return cacheFileMap.computeIfAbsent(fileID, 
id -> {
 
  378                 return Paths.get(cacheDirectory, 
"thumbnails", fileID + 
".png").toFile(); 
 
  379             } 
catch (IllegalStateException e) {
 
  380                 LOGGER.log(Level.WARNING, 
"Could not get cached thumbnail location.  No case is open."); 
 
  411             byte[] fileHeaderBuffer = 
readHeader(file, 2);
 
  416             return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
 
  433         byte[] fileHeaderBuffer;
 
  437             if (length % 2 != 0) {
 
  440             if (length >= 1024) {
 
  443             fileHeaderBuffer = 
readHeader(file, (
int) length); 
 
  449         if (fileHeaderBuffer != null) {
 
  450             for (
int index = 0; index < length; index += 2) {
 
  452                 if ((fileHeaderBuffer[index] == (byte) 0xFF) && (fileHeaderBuffer[index + 1] == (byte) 0xD8)) {
 
  475             byte[] fileHeaderBuffer = 
readHeader(file, 8);
 
  480             return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E)
 
  481                     && ((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D)
 
  482                     && ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
 
  483                     && ((fileHeaderBuffer[7] & 0xff) == 0x0A));
 
  492         byte[] fileHeaderBuffer = 
new byte[buffLength];
 
  493         int bytesRead = file.
read(fileHeaderBuffer, 0, buffLength);
 
  495         if (bytesRead != buffLength) {
 
  499         return fileHeaderBuffer;
 
  514                 "ImageIO could not determine width of {0}: ", 
 
  515                 imageReader -> imageReader.getWidth(0)
 
  531                 "ImageIO could not determine height of {0}: ", 
 
  532                 imageReader -> imageReader.getHeight(0)
 
  548         public T 
extract(ImageReader reader) 
throws IOException;
 
  574                 ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
 
  576                 IIOException iioException = 
new IIOException(
"Could not create ImageInputStream.");
 
  577                 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
 
  580             Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
 
  582             if (readers.hasNext()) {
 
  583                 ImageReader reader = readers.next();
 
  584                 reader.setInput(input);
 
  586                     return propertyExtractor.extract(reader);
 
  587                 } 
catch (IOException ex) {
 
  588                     LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
 
  594                 IIOException iioException = 
new IIOException(
"No ImageReader found.");
 
  595                 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
 
  633         @NbBundle.Messages({
"# {0} - file name",
 
  634             "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
 
  636             "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
 
  639             updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.
getName()));
 
  646         protected javafx.scene.image.Image 
call() throws Exception {
 
  655             if (cacheFile != null) {
 
  657                     if (cacheFile.exists()) {
 
  659                             BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
 
  660                             if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == 
iconSize) {
 
  661                                 return SwingFXUtils.toFXImage(cachedThumbnail, null);
 
  663                         } 
catch (Exception ex) {
 
  664                             LOGGER.log(Level.WARNING, 
"ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  676             BufferedImage thumbnail = null;
 
  678                 if (OPEN_CV_LOADED) {
 
  679                     updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.
getName()));
 
  680                     thumbnail = 
VideoUtils.generateVideoThumbnail(file, iconSize);
 
  682                 if (null == thumbnail) {
 
  683                     if (defaultOnFailure) {
 
  686                         throw new IIOException(
"Failed to generate a thumbnail for " + getContentPathSafe(file));
 
  693                 BufferedImage bufferedImage = SwingFXUtils.fromFXImage(
readImage(), null);
 
  694                 if (null == bufferedImage) {
 
  695                     String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
 
  696                     LOGGER.log(Level.WARNING, msg);
 
  697                     throw new IIOException(msg);
 
  699                 updateProgress(-1, 1);
 
  704                 } 
catch (IllegalArgumentException | OutOfMemoryError e) {
 
  706                     LOGGER.log(Level.WARNING, 
"Cropping {0}, because it could not be scaled: " + e.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  708                     final int height = bufferedImage.getHeight();
 
  709                     final int width = bufferedImage.getWidth();
 
  710                     if (iconSize < height || iconSize < width) {
 
  711                         final int cropHeight = Math.min(iconSize, height);
 
  712                         final int cropWidth = Math.min(iconSize, width);
 
  715                         } 
catch (Exception cropException) {
 
  716                             LOGGER.log(Level.WARNING, 
"Could not crop {0}: " + cropException.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  719                 } 
catch (Exception e) {
 
  720                     LOGGER.log(Level.WARNING, 
"Could not scale {0}: " + e.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  729             updateProgress(-1, 1);
 
  732             if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
 
  736             return SwingFXUtils.toFXImage(thumbnail, null);
 
  748                         Files.createParentDirs(cacheFile);
 
  749                         if (cacheFile.exists()) {
 
  752                         ImageIO.write(thumbnail, FORMAT, cacheFile);
 
  754                 } 
catch (Exception ex) {
 
  755                     LOGGER.log(Level.WARNING, 
"Could not write thumbnail for {0}: " + ex.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  784         "ReadImageTask.mesageText=Reading image: {0}"})
 
  789             updateMessage(Bundle.ReadImageTask_mesageText(file.
getName()));
 
  793         protected javafx.scene.image.Image 
call() throws Exception {
 
  801     static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
 
  810         protected javafx.scene.image.Image 
readImage() throws IOException {
 
  814                 if (image.isError() == 
false) {
 
  822                 javafx.scene.image.Image image = 
new javafx.scene.image.Image(
new BufferedInputStream(readContentInputStream));
 
  823                 if (image.isError() == 
false) {
 
  841                         ImageReadParam param = imageReader.getDefaultReadParam();
 
  842                         BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
 
  843                         param.setDestination(bufferedImage);
 
  845                             bufferedImage = imageReader.
read(0, param); 
 
  846                         } 
catch (IOException iOException) {
 
  847                             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + iOException.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  854                         return SwingFXUtils.toFXImage(bufferedImage, null);
 
  862             updateProgress(percentageDone, 100);
 
  864                 reader.removeIIOReadProgressListener(
this);
 
  874                 javafx.scene.image.Image fxImage = 
get();
 
  875                 if (fxImage == null) {
 
  876                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, 
ImageUtils.getContentPathSafe(file));
 
  877                 } 
else if (fxImage.isError()) {
 
  879                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + ObjectUtils.toString(fxImage.getException()), 
ImageUtils.getContentPathSafe(file));
 
  881             } 
catch (InterruptedException | ExecutionException ex) {
 
  889             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + ObjectUtils.toString(getException()), 
ImageUtils.getContentPathSafe(file));
 
  894             updateProgress(100, 100);
 
  934     static String getContentPathSafe(
Content content) {
 
  937         } 
catch (TskCoreException tskCoreException) {
 
  938             String contentName = content.
getName();
 
  939             LOGGER.log(Level.SEVERE, 
"Failed to get unique path for " + contentName, tskCoreException); 
 
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)
 
long seek(long newPosition)
 
void readAborted(ImageReader source)
 
static Task< javafx.scene.image.Image > newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
 
static final boolean OPEN_CV_LOADED
 
String getNameExtension()
 
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 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)
 
int read(byte[] buf, long offset, long len)
 
static final int ICON_SIZE_MEDIUM
 
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 final ConcurrentHashMap< Long, File > cacheFileMap
 
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 BufferedInputStream getBufferedReadContentStream(AbstractFile file)
 
static boolean isImageThumbnailSupported(AbstractFile file)
 
static BufferedImage getThumbnail(Content content, int iconSize)
 
static final int ICON_SIZE_LARGE
 
final int read(byte[] buf, long offset, long len)
 
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 void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
 
static synchronized BufferedImage cropImage(BufferedImage input, int width, int height)
 
static SortedSet< String > getSupportedImageMimeTypes()