19 package org.sleuthkit.autopsy.discovery.ui;
 
   21 import com.google.common.io.Files;
 
   22 import java.awt.Component;
 
   23 import java.awt.Dimension;
 
   24 import java.awt.Image;
 
   25 import java.awt.Point;
 
   26 import java.awt.image.BufferedImage;
 
   27 import java.io.IOException;
 
   28 import java.nio.file.Paths;
 
   29 import java.util.ArrayList;
 
   30 import java.util.Collections;
 
   31 import java.util.HashMap;
 
   32 import java.util.List;
 
   34 import java.util.logging.Level;
 
   35 import javax.imageio.ImageIO;
 
   36 import javax.swing.ImageIcon;
 
   37 import javax.swing.JComponent;
 
   38 import javax.swing.JOptionPane;
 
   39 import javax.swing.JScrollPane;
 
   40 import javax.swing.JTextPane;
 
   41 import org.apache.commons.io.FileUtils;
 
   42 import org.apache.commons.io.FilenameUtils;
 
   43 import org.imgscalr.Scalr;
 
   44 import org.netbeans.api.progress.ProgressHandle;
 
   45 import org.opencv.core.Mat;
 
   46 import org.opencv.highgui.VideoCapture;
 
   47 import org.openide.util.ImageUtilities;
 
   48 import org.openide.util.NbBundle;
 
   70 final class DiscoveryUiUtils {
 
   72     private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
 
   73     private static final int BYTE_UNIT_CONVERSION = 1000;
 
   74     private static final int ICON_SIZE = 16;
 
   75     private static final String RED_CIRCLE_ICON_PATH = 
"org/sleuthkit/autopsy/images/red-circle-exclamation.png";
 
   76     private static final String YELLOW_CIRCLE_ICON_PATH = 
"org/sleuthkit/autopsy/images/yellow-circle-yield.png";
 
   77     private static final String DELETE_ICON_PATH = 
"org/sleuthkit/autopsy/images/file-icon-deleted.png";
 
   78     private static final String UNSUPPORTED_DOC_PATH = 
"org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
 
   79     private static final ImageIcon INTERESTING_SCORE_ICON = 
new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, 
false));
 
   80     private static final ImageIcon NOTABLE_SCORE_ICON = 
new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, 
false));
 
   81     private static final ImageIcon DELETED_ICON = 
new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, 
false));
 
   82     private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = 
new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, 
false));
 
   83     private static final String THUMBNAIL_FORMAT = 
"png"; 
 
   84     private static final String VIDEO_THUMBNAIL_DIR = 
"video-thumbnails"; 
 
   85     private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
 
   87     @NbBundle.Messages({
"# {0} - fileSize",
 
   89         "DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
 
   90         "DiscoveryUiUtility.bytes.text=bytes",
 
   91         "DiscoveryUiUtility.kiloBytes.text=KB",
 
   92         "DiscoveryUiUtility.megaBytes.text=MB",
 
   93         "DiscoveryUiUtility.gigaBytes.text=GB",
 
   94         "DiscoveryUiUtility.terraBytes.text=TB"})
 
  103     static String getFileSizeString(
long bytes) {
 
  105         int unitsSwitchValue = 0;
 
  106         while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
 
  107             size /= BYTE_UNIT_CONVERSION;
 
  111         switch (unitsSwitchValue) {
 
  113                 units = Bundle.DiscoveryUiUtility_kiloBytes_text();
 
  116                 units = Bundle.DiscoveryUiUtility_megaBytes_text();
 
  119                 units = Bundle.DiscoveryUiUtility_gigaBytes_text();
 
  122                 units = Bundle.DiscoveryUiUtility_terraBytes_text();
 
  125                 units = Bundle.DiscoveryUiUtility_bytes_text();
 
  128         return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
 
  137     static ImageIcon getUnsupportedImageThumbnail() {
 
  138         return UNSUPPORTED_DOCUMENT_THUMBNAIL;
 
  153     static List<String> getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
 
  154         List<BlackboardArtifact> arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
 
  155         List<String> setNames = 
new ArrayList<>();
 
  156         for (BlackboardArtifact art : arts) {
 
  157             for (BlackboardAttribute attr : art.getAttributes()) {
 
  158                 if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
 
  159                     String setName = attr.getValueString();
 
  160                     if (!setNames.contains(setName)) {
 
  161                         setNames.add(setName);
 
  166         Collections.sort(setNames);
 
  178     @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
 
  179     static 
boolean isPointOnIcon(Component comp, Point point) {
 
  180         return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
 
  191     @NbBundle.Messages({
"DiscoveryUiUtils.isDeleted.text=All instances of file are deleted."})
 
  192     @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
 
  193     static void setDeletedIcon(
boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
 
  195             isDeletedLabel.setIcon(DELETED_ICON);
 
  196             isDeletedLabel.setToolTipText(Bundle.DiscoveryUiUtils_isDeleted_text());
 
  198             isDeletedLabel.setIcon(null);
 
  199             isDeletedLabel.setToolTipText(null);
 
  210     @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
 
  211     static 
void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
 
  212         ImageIcon icon = null;
 
  214         Score score = resultFile.getScore();
 
  215         if (score != null && score.getSignificance() != null) {
 
  216             switch (score.getSignificance()) {
 
  218                     icon = NOTABLE_SCORE_ICON;
 
  221                     icon = INTERESTING_SCORE_ICON;
 
  232         scoreLabel.setIcon(icon);
 
  233         scoreLabel.setToolTipText(resultFile.getScoreDescription());
 
  242     static int getIconSize() {
 
  250     @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
 
  251     @NbBundle.Messages({
"DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete"})
 
  252     static void displayErrorMessage(DiscoveryDialog dialog) {
 
  255             SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
 
  256             Map<Long, DataSourceModulesWrapper> dataSourceIngestModules = 
new HashMap<>();
 
  257             for (DataSource dataSource : skCase.getDataSources()) {
 
  258                 dataSourceIngestModules.put(dataSource.getId(), 
new DataSourceModulesWrapper(dataSource.getName()));
 
  261             for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
 
  262                 dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
 
  265             for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
 
  266                 message += dsmodulesWrapper.getMessage();
 
  268             if (!message.isEmpty()) {
 
  269                 JScrollPane messageScrollPane = 
new JScrollPane();
 
  270                 JTextPane messageTextPane = 
new JTextPane();
 
  271                 messageTextPane.setText(message);
 
  272                 messageTextPane.setVisible(
true);
 
  273                 messageTextPane.setEditable(
false);
 
  274                 messageTextPane.setCaretPosition(0);
 
  275                 messageScrollPane.setMaximumSize(
new Dimension(600, 100));
 
  276                 messageScrollPane.setPreferredSize(
new Dimension(600, 100));
 
  277                 messageScrollPane.setViewportView(messageTextPane);
 
  278                 JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.DiscoveryUiUtils_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
 
  280         } 
catch (NoCurrentCaseException | TskCoreException ex) {
 
  281             logger.log(Level.WARNING, 
"Exception while determining which modules have been run for Discovery", ex);
 
  283         dialog.validateDialog();
 
  295     @NbBundle.Messages({
"# {0} - file name",
 
  296         "DiscoveryUiUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
 
  297     static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
 
  298         AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
 
  299         String cacheDirectory;
 
  301             cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
 
  302         } 
catch (NoCurrentCaseException ex) {
 
  303             cacheDirectory = null;
 
  304             logger.log(Level.WARNING, 
"Unable to get cache directory, video thumbnails will not be saved", ex);
 
  306         if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
 
  307             java.io.File tempFile;
 
  309                 tempFile = getVideoFileInTempDir(file);
 
  310             } 
catch (NoCurrentCaseException ex) {
 
  311                 logger.log(Level.WARNING, 
"Exception while getting open case.", ex); 
 
  312                 int[] framePositions = 
new int[]{
 
  317                 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
 
  320             if (tempFile.exists() == 
false || tempFile.length() < file.getSize()) {
 
  321                 ProgressHandle progress = ProgressHandle.createHandle(Bundle.DiscoveryUiUtils_genVideoThumb_progress_text(file.getName()));
 
  324                     Files.createParentDirs(tempFile);
 
  325                     if (Thread.interrupted()) {
 
  326                         int[] framePositions = 
new int[]{
 
  331                         thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
 
  334                     ContentUtils.writeToFile(file, tempFile, progress, null, 
true);
 
  335                 } 
catch (IOException ex) {
 
  336                     logger.log(Level.WARNING, 
"Error extracting temporary file for " + file.getParentPath() + 
"/" + file.getName(), ex); 
 
  341             VideoCapture videoFile = 
new VideoCapture(); 
 
  342             BufferedImage bufferedImage = null;
 
  345                 if (!videoFile.open(tempFile.toString())) {
 
  346                     logger.log(Level.WARNING, 
"Error opening {0} for preview generation.", file.getParentPath() + 
"/" + file.getName()); 
 
  347                     int[] framePositions = 
new int[]{
 
  352                     thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
 
  355                 double fps = videoFile.get(5); 
 
  356                 double totalFrames = videoFile.get(7); 
 
  357                 if (fps <= 0 || totalFrames <= 0) {
 
  358                     logger.log(Level.WARNING, 
"Error getting fps or total frames for {0}", file.getParentPath() + 
"/" + file.getName()); 
 
  359                     int[] framePositions = 
new int[]{
 
  364                     thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
 
  367                 if (Thread.interrupted()) {
 
  368                     int[] framePositions = 
new int[]{
 
  373                     thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
 
  377                 double duration = 1000 * (totalFrames / fps); 
 
  379                 int[] framePositions = 
new int[]{
 
  380                     (int) (duration * .01),
 
  381                     (int) (duration * .25),
 
  382                     (int) (duration * .5),
 
  383                     (int) (duration * .75),};
 
  385                 Mat imageMatrix = 
new Mat();
 
  386                 List<Image> videoThumbnails = 
new ArrayList<>();
 
  387                 if (cacheDirectory == null || file.getMd5Hash() == null) {
 
  388                     cacheDirectory = null;
 
  391                         FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
 
  392                     } 
catch (IOException ex) {
 
  393                         cacheDirectory = null;
 
  394                         logger.log(Level.WARNING, 
"Unable to make video thumbnails directory, thumbnails will not be saved", ex);
 
  397                 for (
int i = 0; i < framePositions.length; i++) {
 
  398                     if (!videoFile.set(0, framePositions[i])) {
 
  399                         logger.log(Level.WARNING, 
"Error seeking to " + framePositions[i] + 
"ms in {0}", file.getParentPath() + 
"/" + file.getName()); 
 
  402                         videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
 
  403                         if (cacheDirectory != null) {
 
  405                                 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
 
  406                                         Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + 
"-" + framePositions[i] + 
"." + THUMBNAIL_FORMAT).toFile()); 
 
  407                             } 
catch (IOException ex) {
 
  408                                 logger.log(Level.WARNING, 
"Unable to save default video thumbnail for " + file.getMd5Hash() + 
" at frame position " + framePositions[i], ex);
 
  414                     if (!videoFile.read(imageMatrix)) {
 
  415                         logger.log(Level.WARNING, 
"Error reading frame at " + framePositions[i] + 
"ms from {0}", file.getParentPath() + 
"/" + file.getName()); 
 
  417                         videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
 
  418                         if (cacheDirectory != null) {
 
  420                                 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
 
  421                                         Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + 
"-" + framePositions[i] + 
"." + THUMBNAIL_FORMAT).toFile()); 
 
  422                             } 
catch (IOException ex) {
 
  423                                 logger.log(Level.WARNING, 
"Unable to save default video thumbnail for " + file.getMd5Hash() + 
" at frame position " + framePositions[i], ex);
 
  430                     if (imageMatrix.empty()) {
 
  431                         videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
 
  432                         if (cacheDirectory != null) {
 
  434                                 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
 
  435                                         Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + 
"-" + framePositions[i] + 
"." + THUMBNAIL_FORMAT).toFile()); 
 
  436                             } 
catch (IOException ex) {
 
  437                                 logger.log(Level.WARNING, 
"Unable to save default video thumbnail for " + file.getMd5Hash() + 
" at frame position " + framePositions[i], ex);
 
  443                     int matrixColumns = imageMatrix.cols();
 
  444                     int matrixRows = imageMatrix.rows();
 
  447                     if (bufferedImage == null) {
 
  448                         bufferedImage = 
new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
 
  451                     byte[] data = 
new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
 
  452                     imageMatrix.get(0, 0, data); 
 
  454                     if (imageMatrix.channels() == 3) {
 
  455                         for (
int k = 0; k < data.length; k += 3) {
 
  457                             data[k] = data[k + 2];
 
  462                     bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
 
  463                     if (Thread.interrupted()) {
 
  464                         thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
 
  466                             FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
 
  467                         } 
catch (IOException ex) {
 
  468                             logger.log(Level.WARNING, 
"Unable to delete directory for cancelled video thumbnail process", ex);
 
  472                     BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
 
  474                     videoThumbnails.add(thumbnail);
 
  475                     if (cacheDirectory != null) {
 
  477                             ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
 
  478                                     Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + 
"-" + framePositions[i] + 
"." + THUMBNAIL_FORMAT).toFile()); 
 
  479                         } 
catch (IOException ex) {
 
  480                             logger.log(Level.WARNING, 
"Unable to save video thumbnail for " + file.getMd5Hash() + 
" at frame position " + framePositions[i], ex);
 
  484                 thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
 
  489             loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
 
  498     private static BufferedImage getDefaultVideoThumbnail() {
 
  500             return ImageIO.read(ImageUtils.class
 
  501                     .getResourceAsStream(
"/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));
 
  502         } 
catch (IOException ex) {
 
  503             logger.log(Level.SEVERE, 
"Failed to load 'failed to create video' placeholder.", ex); 
 
  518     private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
 
  519         int[] framePositions = 
new int[4];
 
  520         List<Image> videoThumbnails = 
new ArrayList<>();
 
  521         int thumbnailNumber = 0;
 
  522         String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
 
  523         for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
 
  525                 videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
 
  526             } 
catch (IOException ex) {
 
  527                 videoThumbnails.add(failedVideoThumbImage);
 
  528                 logger.log(Level.WARNING, 
"Unable to read saved video thumbnail " + fileName + 
" for " + md5, ex);
 
  530             int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
 
  531             framePositions[thumbnailNumber] = framePos;
 
  534         thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
 
  543     private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
 
  544         List<Image> videoThumbnails = 
new ArrayList<>();
 
  545         videoThumbnails.add(failedVideoThumbImage);
 
  546         videoThumbnails.add(failedVideoThumbImage);
 
  547         videoThumbnails.add(failedVideoThumbImage);
 
  548         videoThumbnails.add(failedVideoThumbImage);
 
  549         return videoThumbnails;
 
  555     private DiscoveryUiUtils() {
 
static File getVideoFileInTempDir(AbstractFile file)