diff --git a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties index 4d95cba42c..7bd6347392 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties @@ -56,4 +56,3 @@ ResultsPanel.currentPageLabel.text=Page: - ResultsPanel.pageControlsLabel.text=Pages: ResultsPanel.gotoPageLabel.text=Go to Page: ResultsPanel.pageSizeLabel.text=Page size: -ThumbnailPanel.fileInfoLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryEvents.java b/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryEvents.java index 07c6545495..48fe0cc41d 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryEvents.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryEvents.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.filequery; import com.google.common.eventbus.EventBus; import java.util.Collections; import java.util.List; -import java.util.LinkedHashMap; import java.util.Map; import org.sleuthkit.autopsy.filequery.FileSearchData.FileType; diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileDiscoveryDialog.java b/Core/src/org/sleuthkit/autopsy/filequery/FileDiscoveryDialog.java index d9bcceb1bc..f5b332f599 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileDiscoveryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileDiscoveryDialog.java @@ -61,6 +61,8 @@ class FileDiscoveryDialog extends javax.swing.JDialog { leftSplitPane.setRightComponent(groupListPanel); rightSplitPane.setTopComponent(resultsPanel); rightSplitPane.setBottomComponent(dataContentPanel); + //add list selection listener so the content viewer will be updated with the selected file + //when a file is selected in the results panel resultsPanel.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileGroup.java b/Core/src/org/sleuthkit/autopsy/filequery/FileGroup.java index e4800899ee..0695d06f9c 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileGroup.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileGroup.java @@ -21,9 +21,6 @@ package org.sleuthkit.autopsy.filequery; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; - -import org.sleuthkit.datamodel.AbstractFile; /** * Class for storing files that belong to a particular group. diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java index dc30494e74..ad824ba1b5 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java @@ -335,20 +335,29 @@ class FileSearch { + "AND blackboard_artifacts.obj_id IN (" + objIdList + ") "; // NON-NLS } + /** + * Get the video thumbnails for a specified AbstractFile. + * + * @param file Video file to generate thumbnails for. + * + * @return An object containing the list of video thumbnails, an array of + * their timestamps, and the AbstractFile they were generated for. + */ @NbBundle.Messages({"# {0} - file name", "FileSearch.genVideoThumb.progress.text=extracting temporary file {0}"}) - static ThumbnailsWrapper createVideoThumbnails(AbstractFile file) { + static VideoThumbnailsWrapper getVideoThumbnails(AbstractFile file) { + //Currently this method always creates the thumbnails java.io.File tempFile; try { tempFile = getVideoFileInTempDir(file); } catch (NoCurrentCaseException ex) { -// LOGGER.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS + logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS int[] framePositions = new int[]{ 0, 0, 0, 0}; - return new ThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); + return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); } if (tempFile.exists() == false || tempFile.length() < file.getSize()) { ProgressHandle progress = ProgressHandle.createHandle(Bundle.FileSearch_genVideoThumb_progress_text(file.getName())); @@ -361,11 +370,11 @@ class FileSearch { 0, 0, 0}; - return new ThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); + return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); } ContentUtils.writeToFile(file, tempFile, progress, null, true); } catch (IOException ex) { -// LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS + logger.log(Level.WARNING, "Error extracting temporary file for " + file.getParentPath() + "/" + file.getName(), ex); //NON-NLS } finally { progress.finish(); } @@ -375,24 +384,24 @@ class FileSearch { try { if (!videoFile.open(tempFile.toString())) { -// LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS + logger.log(Level.WARNING, "Error opening {0} for preview generation.", file.getParentPath() + "/" + file.getName()); //NON-NLS int[] framePositions = new int[]{ 0, 0, 0, 0}; - return new ThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); + return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); } double fps = videoFile.get(5); // gets frame per second double totalFrames = videoFile.get(7); // gets total frames if (fps <= 0 || totalFrames <= 0) { -// LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS + logger.log(Level.WARNING, "Error getting fps or total frames for {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS int[] framePositions = new int[]{ 0, 0, 0, 0}; - return new ThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); + return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); } if (Thread.interrupted()) { int[] framePositions = new int[]{ @@ -400,22 +409,11 @@ class FileSearch { 0, 0, 0}; - return new ThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); + return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file); } double duration = 1000 * (totalFrames / fps); //total milliseconds - /* - * Four attempts are made to grab a frame from a video. The first - * attempt at 50% will give us a nice frame in the middle that gets - * to the heart of the content. If that fails, the next positions - * tried will be 25% and 75%. After three failed attempts, 1% will - * be tried in a last-ditch effort, the idea being the video may be - * corrupt and that our best chance at retrieving a frame is early - * on in the video. - * - * If no frame can be retrieved, no thumbnail will be created. - */ int[] framePositions = new int[]{ (int) (duration * .01), (int) (duration * .25), @@ -426,7 +424,7 @@ class FileSearch { List videoThumbnails = new ArrayList<>(); for (int i = 0; i < framePositions.length; i++) { if (!videoFile.set(0, framePositions[i])) { -// LOGGER.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS + logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS // If we can't set the time, continue to the next frame position and try again. videoThumbnails.add(ImageUtils.getDefaultThumbnail()); @@ -434,7 +432,7 @@ class FileSearch { } // Read the frame into the image/matrix. if (!videoFile.read(imageMatrix)) { -// LOGGER.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS + logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS // If the image is bad for some reason, continue to the next frame position and try again. videoThumbnails.add(ImageUtils.getDefaultThumbnail()); continue; @@ -456,7 +454,6 @@ class FileSearch { byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())]; imageMatrix.get(0, 0, data); //copy the image to data - //todo: this looks like we are swapping the first and third channels. so we can use BufferedImage.TYPE_3BYTE_BGR if (imageMatrix.channels() == 3) { for (int k = 0; k < data.length; k += 3) { byte temp = data[k]; @@ -467,11 +464,11 @@ class FileSearch { bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data); if (Thread.interrupted()) { - return new ThumbnailsWrapper(videoThumbnails, framePositions, file); + return new VideoThumbnailsWrapper(videoThumbnails, framePositions, file); } - videoThumbnails.add(bufferedImage == null ? ImageUtils.getDefaultThumbnail() : ScalrWrapper.resizeFast(bufferedImage, ImageUtils.ICON_SIZE_LARGE)); + videoThumbnails.add(ScalrWrapper.resizeFast(bufferedImage, ImageUtils.ICON_SIZE_LARGE)); } - return new ThumbnailsWrapper(videoThumbnails, framePositions, file); + return new VideoThumbnailsWrapper(videoThumbnails, framePositions, file); } finally { videoFile.release(); // close the file} } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java index 5db077c44f..cb815fe4d2 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java @@ -59,7 +59,7 @@ public class ResultsPanel extends javax.swing.JPanel { private final EamDb centralRepo; private int groupSize = 0; private PageWorker pageWorker; - private final List thumbnailWorkers = new ArrayList<>(); + private final List thumbnailWorkers = new ArrayList<>(); /** * Creates new form ResultsPanel. @@ -122,8 +122,8 @@ public class ResultsPanel extends javax.swing.JPanel { } void populateVideoViewer(List files) { - //cancell any unfished thumb workers - for (ThumbnailWorker thumbWorker : thumbnailWorkers) { + //cancel any unfished thumb workers + for (SwingWorker thumbWorker : thumbnailWorkers) { if (!thumbWorker.isDone()) { thumbWorker.cancel(true); } @@ -132,9 +132,9 @@ public class ResultsPanel extends javax.swing.JPanel { thumbnailWorkers.clear(); videoThumbnailViewer.clearViewer(); for (ResultFile file : files) { - ThumbnailWorker thumbWorker = new ThumbnailWorker(file); + VideoThumbnailWorker thumbWorker = new VideoThumbnailWorker(file); thumbWorker.execute(); - //keep track of thumb worker for possible cancellation + //keep track of thumb worker for possible cancelation thumbnailWorkers.add(thumbWorker); } } @@ -426,18 +426,18 @@ public class ResultsPanel extends javax.swing.JPanel { private javax.swing.JPanel resultsViewerPanel; // End of variables declaration//GEN-END:variables - private class ThumbnailWorker extends SwingWorker { + private class VideoThumbnailWorker extends SwingWorker { private final ResultFile file; - private ThumbnailsWrapper thumbnailWrapper; + private VideoThumbnailsWrapper thumbnailWrapper; - ThumbnailWorker(ResultFile file) { + VideoThumbnailWorker(ResultFile file) { this.file = file; } @Override protected Void doInBackground() throws Exception { - thumbnailWrapper = FileSearch.createVideoThumbnails(file.getAbstractFile()); + thumbnailWrapper = FileSearch.getVideoThumbnails(file.getAbstractFile()); return null; } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java b/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java index bd06752caf..91bb93e865 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java @@ -24,9 +24,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; -import org.sleuthkit.datamodel.AbstractFile; /** * Class to hold the results of the filtering/grouping/sorting operations diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ThumbnailPanel.form b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.form similarity index 87% rename from Core/src/org/sleuthkit/autopsy/filequery/ThumbnailPanel.form rename to Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.form index 54b3e4c23b..f1a41a51e0 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/ThumbnailPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.form @@ -51,11 +51,6 @@ - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ThumbnailPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.java similarity index 80% rename from Core/src/org/sleuthkit/autopsy/filequery/ThumbnailPanel.java rename to Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.java index 76e3fc3f2c..78726fd84b 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/ThumbnailPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.filequery; @@ -16,23 +29,28 @@ import javax.swing.JList; import javax.swing.ListCellRenderer; /** - * + * Class which displays thumbnails and information for a video file. */ -public final class ThumbnailPanel extends javax.swing.JPanel implements ListCellRenderer { +public final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRenderer { private static final int GAP_SIZE = 4; private static final Color SELECTION_COLOR = new Color(100, 200, 255); private static final long serialVersionUID = 1L; /** - * Creates new form ThumbnailPanel + * Creates new form VideoThumbnailPanel */ - public ThumbnailPanel() { + public VideoThumbnailPanel() { initComponents(); this.setFocusable(true); } - private void addThumbnails(ThumbnailsWrapper thumbnailWrapper) { + /** + * Add the thumbnails to the panel. + * + * @param thumbnailWrapper + */ + private void addThumbnails(VideoThumbnailsWrapper thumbnailWrapper) { imagePanel.removeAll(); GridBagConstraints gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; @@ -81,8 +99,6 @@ public final class ThumbnailPanel extends javax.swing.JPanel implements ListCell imagePanel.setLayout(new java.awt.GridBagLayout()); - org.openide.awt.Mnemonics.setLocalizedText(fileInfoLabel, org.openide.util.NbBundle.getMessage(ThumbnailPanel.class, "ThumbnailPanel.fileInfoLabel.text")); // NOI18N - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -112,8 +128,8 @@ public final class ThumbnailPanel extends javax.swing.JPanel implements ListCell // End of variables declaration//GEN-END:variables @Override - public Component getListCellRendererComponent(JList list, ThumbnailsWrapper value, int index, boolean isSelected, boolean cellHasFocus) { - fileInfoLabel.setText(value.getFileInfo()); + public Component getListCellRendererComponent(JList list, VideoThumbnailsWrapper value, int index, boolean isSelected, boolean cellHasFocus) { + fileInfoLabel.setText(value.getFilePath()); addThumbnails(value); imagePanel.setBackground(isSelected ? SELECTION_COLOR : list.getBackground()); setBackground(isSelected ? SELECTION_COLOR : list.getBackground()); diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.form b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.form index d08b44f33a..a37a93abcb 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.form +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.form @@ -35,14 +35,14 @@ - + - + - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java index cb92998e0a..f46dbff906 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.filequery; @@ -10,13 +23,13 @@ import javax.swing.event.ListSelectionListener; import org.sleuthkit.datamodel.AbstractFile; /** + * A JPanel to display video thumbnails. * - * @author wschaefer */ public class VideoThumbnailViewer extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private final DefaultListModel thumbnailListModel = new DefaultListModel<>(); + private final DefaultListModel thumbnailListModel = new DefaultListModel<>(); /** * Creates new form VideoThumbnailViewer @@ -25,10 +38,21 @@ public class VideoThumbnailViewer extends javax.swing.JPanel { initComponents(); } + /** + * Add a selection listener to the list of thumbnails being displayed. + * + * @param listener The ListSelectionListener to add to the selection model. + */ void addListSelectionListener(ListSelectionListener listener) { thumbnailList.getSelectionModel().addListSelectionListener(listener); } + /** + * Get the AbstractFile associated with the selected thumbnails. + * + * @return The AbstractFile associated with the selected thumbnails, or null + * if no thumnails are selected. + */ AbstractFile getSelectedFile() { if (thumbnailList.getSelectedIndex() == -1) { return null; @@ -37,13 +61,22 @@ public class VideoThumbnailViewer extends javax.swing.JPanel { } } + /** + * Clear the list of thumbnails being displayed. + */ void clearViewer() { synchronized (this) { thumbnailListModel.removeAllElements(); } } - void addRow(ThumbnailsWrapper thumbnailWrapper) { + /** + * Add thumbnails for a video to the panel. + * + * @param thumbnailWrapper The object which contains the thumbnails which + * will be displayed. + */ + void addRow(VideoThumbnailsWrapper thumbnailWrapper) { synchronized (this) { thumbnailListModel.addElement(thumbnailWrapper); } @@ -65,7 +98,7 @@ public class VideoThumbnailViewer extends javax.swing.JPanel { thumbnailList.setModel(thumbnailListModel); thumbnailList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - thumbnailList.setCellRenderer(new ThumbnailPanel()); + thumbnailList.setCellRenderer(new VideoThumbnailPanel()); thumbnailListScrollPane.setViewportView(thumbnailList); add(thumbnailListScrollPane, java.awt.BorderLayout.CENTER); @@ -73,7 +106,7 @@ public class VideoThumbnailViewer extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JList thumbnailList; + private javax.swing.JList thumbnailList; private javax.swing.JScrollPane thumbnailListScrollPane; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ThumbnailsWrapper.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java similarity index 51% rename from Core/src/org/sleuthkit/autopsy/filequery/ThumbnailsWrapper.java rename to Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java index 2e12e7ee23..2d8747b492 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/ThumbnailsWrapper.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java @@ -25,30 +25,59 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; /** - * - * @author wschaefer + * Class to wrap all the information necessary for video thumbnails to be + * displayed. */ -public class ThumbnailsWrapper { +public class VideoThumbnailsWrapper { private final List thumbnails; private final AbstractFile abstractFile; private final int[] timeStamps; - public ThumbnailsWrapper(List thumbnails, int[] timeStamps, AbstractFile file) { + /** + * Construct a new VideoThumbnailsWrapper. + * + * @param thumbnails The list of Images which are the thumbnails for the + * video. + * @param timeStamps An array containing the time in milliseconds into the + * video that each thumbnail created for. + * @param file The AbstractFile which represents the video file which + * the thumbnails were created for. + */ + public VideoThumbnailsWrapper(List thumbnails, int[] timeStamps, AbstractFile file) { this.thumbnails = thumbnails; this.timeStamps = timeStamps; this.abstractFile = file; } + /** + * Get the AbstractFile which represents the video file which the thumbnails + * were created for. + * + * @return The AbstractFile which represents the video file which the + * thumbnails were created for. + */ AbstractFile getAbstractFile() { return abstractFile; } + /** + * Get the array containing thumbnail timestamps. Each timestamp is stored + * as the number of milliseconds into the video each thumbnail was created + * at. + * + * @return The array of timestamps in milliseconds from start of video. + */ int[] getTimeStamps() { return timeStamps.clone(); } - String getFileInfo() { + /** + * Get the path to the file including the file name. + * + * @return The path to the file including the file name. + */ + String getFilePath() { try { return abstractFile.getUniquePath(); } catch (TskCoreException ingored) { @@ -56,6 +85,11 @@ public class ThumbnailsWrapper { } } + /** + * Get the list of thumbnails for the video. + * + * @return The list of Images which are the thumbnails for the video. + */ List getThumbnails() { return Collections.unmodifiableList(thumbnails); }