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;
69 final class DiscoveryUiUtils {
71 private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
72 private static final int BYTE_UNIT_CONVERSION = 1000;
73 private static final int ICON_SIZE = 16;
74 private static final String RED_CIRCLE_ICON_PATH =
"org/sleuthkit/autopsy/images/red-circle-exclamation.png";
75 private static final String YELLOW_CIRCLE_ICON_PATH =
"org/sleuthkit/autopsy/images/yellow-circle-yield.png";
76 private static final String DELETE_ICON_PATH =
"org/sleuthkit/autopsy/images/file-icon-deleted.png";
77 private static final String UNSUPPORTED_DOC_PATH =
"org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
78 private static final ImageIcon INTERESTING_SCORE_ICON =
new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH,
false));
79 private static final ImageIcon NOTABLE_SCORE_ICON =
new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH,
false));
80 private static final ImageIcon DELETED_ICON =
new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH,
false));
81 private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL =
new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH,
false));
82 private static final String THUMBNAIL_FORMAT =
"png";
83 private static final String VIDEO_THUMBNAIL_DIR =
"video-thumbnails";
84 private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
86 @NbBundle.Messages({
"# {0} - fileSize",
88 "DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
89 "DiscoveryUiUtility.bytes.text=bytes",
90 "DiscoveryUiUtility.kiloBytes.text=KB",
91 "DiscoveryUiUtility.megaBytes.text=MB",
92 "DiscoveryUiUtility.gigaBytes.text=GB",
93 "DiscoveryUiUtility.terraBytes.text=TB"})
102 static String getFileSizeString(
long bytes) {
104 int unitsSwitchValue = 0;
105 while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
106 size /= BYTE_UNIT_CONVERSION;
110 switch (unitsSwitchValue) {
112 units = Bundle.DiscoveryUiUtility_kiloBytes_text();
115 units = Bundle.DiscoveryUiUtility_megaBytes_text();
118 units = Bundle.DiscoveryUiUtility_gigaBytes_text();
121 units = Bundle.DiscoveryUiUtility_terraBytes_text();
124 units = Bundle.DiscoveryUiUtility_bytes_text();
127 return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
136 static ImageIcon getUnsupportedImageThumbnail() {
137 return UNSUPPORTED_DOCUMENT_THUMBNAIL;
152 static List<String> getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
153 List<BlackboardArtifact> arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
154 List<String> setNames =
new ArrayList<>();
155 for (BlackboardArtifact art : arts) {
156 for (BlackboardAttribute attr : art.getAttributes()) {
157 if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
158 String setName = attr.getValueString();
159 if (!setNames.contains(setName)) {
160 setNames.add(setName);
165 Collections.sort(setNames);
177 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
178 static
boolean isPointOnIcon(Component comp, Point point) {
179 return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
190 @NbBundle.Messages({
"DiscoveryUiUtils.isDeleted.text=All instances of file are deleted."})
191 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
192 static void setDeletedIcon(
boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
194 isDeletedLabel.setIcon(DELETED_ICON);
195 isDeletedLabel.setToolTipText(Bundle.DiscoveryUiUtils_isDeleted_text());
197 isDeletedLabel.setIcon(null);
198 isDeletedLabel.setToolTipText(null);
209 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
210 static
void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
211 switch (resultFile.getScore()) {
213 scoreLabel.setIcon(NOTABLE_SCORE_ICON);
215 case INTERESTING_SCORE:
216 scoreLabel.setIcon(INTERESTING_SCORE_ICON);
220 scoreLabel.setIcon(null);
223 scoreLabel.setToolTipText(resultFile.getScoreDescription());
231 static int getIconSize() {
239 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
240 @NbBundle.Messages({
"DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete"})
241 static void displayErrorMessage(DiscoveryDialog dialog) {
244 SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
245 Map<Long, DataSourceModulesWrapper> dataSourceIngestModules =
new HashMap<>();
246 for (DataSource dataSource : skCase.getDataSources()) {
247 dataSourceIngestModules.put(dataSource.getId(),
new DataSourceModulesWrapper(dataSource.getName()));
250 for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
251 dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
254 for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
255 message += dsmodulesWrapper.getMessage();
257 if (!message.isEmpty()) {
258 JScrollPane messageScrollPane =
new JScrollPane();
259 JTextPane messageTextPane =
new JTextPane();
260 messageTextPane.setText(message);
261 messageTextPane.setVisible(
true);
262 messageTextPane.setEditable(
false);
263 messageTextPane.setCaretPosition(0);
264 messageScrollPane.setMaximumSize(
new Dimension(600, 100));
265 messageScrollPane.setPreferredSize(
new Dimension(600, 100));
266 messageScrollPane.setViewportView(messageTextPane);
267 JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.DiscoveryUiUtils_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
269 }
catch (NoCurrentCaseException | TskCoreException ex) {
270 logger.log(Level.WARNING,
"Exception while determining which modules have been run for Discovery", ex);
272 dialog.validateDialog();
284 @NbBundle.Messages({
"# {0} - file name",
285 "DiscoveryUiUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
286 static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
287 AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
288 String cacheDirectory;
290 cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
291 }
catch (NoCurrentCaseException ex) {
292 cacheDirectory = null;
293 logger.log(Level.WARNING,
"Unable to get cache directory, video thumbnails will not be saved", ex);
295 if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
296 java.io.File tempFile;
298 tempFile = getVideoFileInTempDir(file);
299 }
catch (NoCurrentCaseException ex) {
300 logger.log(Level.WARNING,
"Exception while getting open case.", ex);
301 int[] framePositions =
new int[]{
306 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
309 if (tempFile.exists() ==
false || tempFile.length() < file.getSize()) {
310 ProgressHandle progress = ProgressHandle.createHandle(Bundle.DiscoveryUiUtils_genVideoThumb_progress_text(file.getName()));
313 Files.createParentDirs(tempFile);
314 if (Thread.interrupted()) {
315 int[] framePositions =
new int[]{
320 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
323 ContentUtils.writeToFile(file, tempFile, progress, null,
true);
324 }
catch (IOException ex) {
325 logger.log(Level.WARNING,
"Error extracting temporary file for " + file.getParentPath() +
"/" + file.getName(), ex);
330 VideoCapture videoFile =
new VideoCapture();
331 BufferedImage bufferedImage = null;
334 if (!videoFile.open(tempFile.toString())) {
335 logger.log(Level.WARNING,
"Error opening {0} for preview generation.", file.getParentPath() +
"/" + file.getName());
336 int[] framePositions =
new int[]{
341 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
344 double fps = videoFile.get(5);
345 double totalFrames = videoFile.get(7);
346 if (fps <= 0 || totalFrames <= 0) {
347 logger.log(Level.WARNING,
"Error getting fps or total frames for {0}", file.getParentPath() +
"/" + file.getName());
348 int[] framePositions =
new int[]{
353 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
356 if (Thread.interrupted()) {
357 int[] framePositions =
new int[]{
362 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
366 double duration = 1000 * (totalFrames / fps);
368 int[] framePositions =
new int[]{
369 (int) (duration * .01),
370 (int) (duration * .25),
371 (int) (duration * .5),
372 (int) (duration * .75),};
374 Mat imageMatrix =
new Mat();
375 List<Image> videoThumbnails =
new ArrayList<>();
376 if (cacheDirectory == null || file.getMd5Hash() == null) {
377 cacheDirectory = null;
380 FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
381 }
catch (IOException ex) {
382 cacheDirectory = null;
383 logger.log(Level.WARNING,
"Unable to make video thumbnails directory, thumbnails will not be saved", ex);
386 for (
int i = 0; i < framePositions.length; i++) {
387 if (!videoFile.set(0, framePositions[i])) {
388 logger.log(Level.WARNING,
"Error seeking to " + framePositions[i] +
"ms in {0}", file.getParentPath() +
"/" + file.getName());
391 videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
392 if (cacheDirectory != null) {
394 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
395 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i +
"-" + framePositions[i] +
"." + THUMBNAIL_FORMAT).toFile());
396 }
catch (IOException ex) {
397 logger.log(Level.WARNING,
"Unable to save default video thumbnail for " + file.getMd5Hash() +
" at frame position " + framePositions[i], ex);
403 if (!videoFile.read(imageMatrix)) {
404 logger.log(Level.WARNING,
"Error reading frame at " + framePositions[i] +
"ms from {0}", file.getParentPath() +
"/" + file.getName());
406 videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
407 if (cacheDirectory != null) {
409 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
410 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i +
"-" + framePositions[i] +
"." + THUMBNAIL_FORMAT).toFile());
411 }
catch (IOException ex) {
412 logger.log(Level.WARNING,
"Unable to save default video thumbnail for " + file.getMd5Hash() +
" at frame position " + framePositions[i], ex);
419 if (imageMatrix.empty()) {
420 videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
421 if (cacheDirectory != null) {
423 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
424 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i +
"-" + framePositions[i] +
"." + THUMBNAIL_FORMAT).toFile());
425 }
catch (IOException ex) {
426 logger.log(Level.WARNING,
"Unable to save default video thumbnail for " + file.getMd5Hash() +
" at frame position " + framePositions[i], ex);
432 int matrixColumns = imageMatrix.cols();
433 int matrixRows = imageMatrix.rows();
436 if (bufferedImage == null) {
437 bufferedImage =
new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
440 byte[] data =
new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
441 imageMatrix.get(0, 0, data);
443 if (imageMatrix.channels() == 3) {
444 for (
int k = 0; k < data.length; k += 3) {
446 data[k] = data[k + 2];
451 bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
452 if (Thread.interrupted()) {
453 thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
455 FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
456 }
catch (IOException ex) {
457 logger.log(Level.WARNING,
"Unable to delete directory for cancelled video thumbnail process", ex);
461 BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
463 videoThumbnails.add(thumbnail);
464 if (cacheDirectory != null) {
466 ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
467 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i +
"-" + framePositions[i] +
"." + THUMBNAIL_FORMAT).toFile());
468 }
catch (IOException ex) {
469 logger.log(Level.WARNING,
"Unable to save video thumbnail for " + file.getMd5Hash() +
" at frame position " + framePositions[i], ex);
473 thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
478 loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
487 private static BufferedImage getDefaultVideoThumbnail() {
489 return ImageIO.read(ImageUtils.class
490 .getResourceAsStream(
"/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));
491 }
catch (IOException ex) {
492 logger.log(Level.SEVERE,
"Failed to load 'failed to create video' placeholder.", ex);
507 private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
508 int[] framePositions =
new int[4];
509 List<Image> videoThumbnails =
new ArrayList<>();
510 int thumbnailNumber = 0;
511 String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
512 for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
514 videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
515 }
catch (IOException ex) {
516 videoThumbnails.add(failedVideoThumbImage);
517 logger.log(Level.WARNING,
"Unable to read saved video thumbnail " + fileName +
" for " + md5, ex);
519 int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
520 framePositions[thumbnailNumber] = framePos;
523 thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
532 private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
533 List<Image> videoThumbnails =
new ArrayList<>();
534 videoThumbnails.add(failedVideoThumbImage);
535 videoThumbnails.add(failedVideoThumbImage);
536 videoThumbnails.add(failedVideoThumbImage);
537 videoThumbnails.add(failedVideoThumbImage);
538 return videoThumbnails;
544 private DiscoveryUiUtils() {
static File getVideoFileInTempDir(AbstractFile file)