diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 463e1c1903..593699e27f 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -223,6 +223,10 @@ ext/StixLib.jar release/modules/ext/StixLib.jar + + ext/opencv-248.jar + release/modules/ext/opencv-248.jar + ext/sqlite-jdbc-3.7.15-M1.jar release/modules/ext/sqlite-jdbc-3.7.15-M1.jar diff --git a/Core/release/modules/ext/jython-standalone-2.7.0.jar b/Core/release/modules/ext/jython-standalone-2.7.0.jar index c49498347a..e7b35d6c5f 100755 Binary files a/Core/release/modules/ext/jython-standalone-2.7.0.jar and b/Core/release/modules/ext/jython-standalone-2.7.0.jar differ diff --git a/Core/release/modules/ext/opencv-248.jar b/Core/release/modules/ext/opencv-248.jar new file mode 100644 index 0000000000..f09b29077d Binary files /dev/null and b/Core/release/modules/ext/opencv-248.jar differ diff --git a/Core/release/modules/lib/amd64/opencv_ffmpeg248_64.dll b/Core/release/modules/lib/amd64/opencv_ffmpeg248_64.dll new file mode 100644 index 0000000000..37236e5424 Binary files /dev/null and b/Core/release/modules/lib/amd64/opencv_ffmpeg248_64.dll differ diff --git a/Core/release/modules/lib/amd64/opencv_java248.dll b/Core/release/modules/lib/amd64/opencv_java248.dll new file mode 100644 index 0000000000..ab989a6b11 Binary files /dev/null and b/Core/release/modules/lib/amd64/opencv_java248.dll differ diff --git a/Core/release/modules/lib/i386/opencv_ffmpeg248.dll b/Core/release/modules/lib/i386/opencv_ffmpeg248.dll new file mode 100644 index 0000000000..b1e70df6a3 Binary files /dev/null and b/Core/release/modules/lib/i386/opencv_ffmpeg248.dll differ diff --git a/Core/release/modules/lib/i386/opencv_java248.dll b/Core/release/modules/lib/i386/opencv_java248.dll new file mode 100644 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/Core/release/modules/lib/i386/opencv_java248.dll differ diff --git a/Core/release/modules/lib/i586/opencv_ffmpeg248_64.dll b/Core/release/modules/lib/i586/opencv_ffmpeg248_64.dll new file mode 100644 index 0000000000..b1e70df6a3 Binary files /dev/null and b/Core/release/modules/lib/i586/opencv_ffmpeg248_64.dll differ diff --git a/Core/release/modules/lib/i586/opencv_java248.dll b/Core/release/modules/lib/i586/opencv_java248.dll new file mode 100644 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/Core/release/modules/lib/i586/opencv_java248.dll differ diff --git a/Core/release/modules/lib/i686/opencv_ffmpeg248_64.dll b/Core/release/modules/lib/i686/opencv_ffmpeg248_64.dll new file mode 100644 index 0000000000..b1e70df6a3 Binary files /dev/null and b/Core/release/modules/lib/i686/opencv_ffmpeg248_64.dll differ diff --git a/Core/release/modules/lib/i686/opencv_java248.dll b/Core/release/modules/lib/i686/opencv_java248.dll new file mode 100644 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/Core/release/modules/lib/i686/opencv_java248.dll differ diff --git a/Core/release/modules/lib/x86/opencv_ffmpeg248.dll b/Core/release/modules/lib/x86/opencv_ffmpeg248.dll new file mode 100644 index 0000000000..b1e70df6a3 Binary files /dev/null and b/Core/release/modules/lib/x86/opencv_ffmpeg248.dll differ diff --git a/Core/release/modules/lib/x86/opencv_java248.dll b/Core/release/modules/lib/x86/opencv_java248.dll new file mode 100644 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/Core/release/modules/lib/x86/opencv_java248.dll differ diff --git a/Core/release/modules/lib/x86_64/opencv_ffmpeg248_64.dll b/Core/release/modules/lib/x86_64/opencv_ffmpeg248_64.dll new file mode 100644 index 0000000000..37236e5424 Binary files /dev/null and b/Core/release/modules/lib/x86_64/opencv_ffmpeg248_64.dll differ diff --git a/Core/release/modules/lib/x86_64/opencv_java248.dll b/Core/release/modules/lib/x86_64/opencv_java248.dll new file mode 100644 index 0000000000..ab989a6b11 Binary files /dev/null and b/Core/release/modules/lib/x86_64/opencv_java248.dll differ diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index 3013703d24..4f87aa8fdf 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -21,9 +21,8 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; -import java.util.Arrays; +import java.util.List; import static java.util.Objects.nonNull; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; @@ -32,7 +31,6 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; -import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; @@ -48,16 +46,18 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; }) public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { - private static final Set AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); private AbstractFile lastFile; //UI private final MediaViewVideoPanel videoPanel; + private final boolean videoPanelInited; private final SortedSet videoExtensions; // get them from the panel private final SortedSet videoMimes; private final MediaViewImagePanel imagePanel; - private final boolean videoPanelInited; private final boolean imagePanelInited; + private final SortedSet imageExtensions; // get them from the panel + private final SortedSet imageMimes; + private static final String IMAGE_VIEWER_LAYER = "IMAGE"; //NON-NLS private static final String VIDEO_VIEWER_LAYER = "VIDEO"; //NON-NLS @@ -71,14 +71,16 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo // get the right panel for our platform videoPanel = MediaViewVideoPanel.createVideoPanel(); videoPanelInited = videoPanel.isInited(); - videoExtensions = new TreeSet<>(Arrays.asList(videoPanel.getExtensions())); + videoExtensions = new TreeSet<>(videoPanel.getExtensionsList()); videoMimes = new TreeSet<>(videoPanel.getMimeTypes()); imagePanel = new MediaViewImagePanel(); imagePanelInited = imagePanel.isInited(); + imageExtensions = new TreeSet<>(imagePanel.getExtensionsList()); + imageMimes = new TreeSet<>(imagePanel.getMimeTypes()); customizeComponents(); - logger.log(Level.INFO, "Created MediaView instance: " + this); //NON-NLS + logger.log(Level.INFO, "Created MediaView instance: {0}", this); //NON-NLS } private void customizeComponents() { @@ -125,12 +127,12 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo final Dimension dims = DataContentViewerMedia.this.getSize(); //logger.info("setting node on media viewer"); //NON-NLS - if (imagePanelInited && isImageSupported(file)) { - imagePanel.showImageFx(file, dims); - this.showVideoPanel(false); - } else if (videoPanelInited && isVideoSupported(file)) { + if (videoPanelInited && videoPanel.isSupported(file)) { videoPanel.setupVideo(file, dims); this.showVideoPanel(true); + } else if (imagePanelInited && imagePanel.isSupported(file)) { + imagePanel.showImageFx(file, dims); + this.showVideoPanel(false); } } catch (Exception e) { logger.log(Level.SEVERE, "Exception while setting node", e); //NON-NLS @@ -186,24 +188,10 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo * @return True if a video file that can be displayed */ private boolean isVideoSupported(AbstractFile file) { - String extension = file.getNameExtension(); - - //TODO: is this what we want, to require both extension and mimetype support? - if (AUDIO_EXTENSIONS.contains("." + extension) || videoExtensions.contains("." + extension)) { - try { - String mimeType = new FileTypeDetector().getFileType(file); - if (nonNull(mimeType)) { - return videoMimes.contains(mimeType); - } - } catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) { - logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex); - if (!videoMimes.isEmpty() && file.isMimeType(videoMimes) == MimeMatchEnum.TRUE) { - return true; - } - } + if (null == file || file.getSize() == 0) { + return false; } - - return false; + return videoPanel.isSupported(file); } /** @@ -215,7 +203,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo */ private boolean isImageSupported(AbstractFile file) { - return ImageUtils.thumbnailSupported(file); + if (null == file || file.getSize() == 0) { + return false; + } + + return imagePanel.isSupported(file); } @Override @@ -256,4 +248,43 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo return 7; } } + + interface MediaViewPanel { + + /** + * @return supported mime types + */ + List getMimeTypes(); + + /** + * returns supported extensions (each starting with .) + * + * @return + */ + List getExtensionsList(); + + default boolean isSupported(AbstractFile file) { + SortedSet mimeTypes = new TreeSet<>(getMimeTypes()); + try { + String mimeType = new FileTypeDetector().getFileType(file); + if (nonNull(mimeType)) { + return mimeTypes.contains(mimeType); + } + } catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) { + logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex); + if (!mimeTypes.isEmpty() && file.isMimeType(mimeTypes) == MimeMatchEnum.TRUE) { + return true; + } + } + + String extension = file.getNameExtension(); + + if (getExtensionsList().contains("." + extension)) { + return true; + } + + return false; + } + + } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form index 0f9063922e..e86a2a5daf 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form @@ -16,34 +16,42 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + - + @@ -66,8 +74,8 @@ - - + + @@ -75,18 +83,6 @@ - - - - - - - - - - - - @@ -213,5 +209,7 @@ + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 37468a169d..c0398ffe27 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -27,9 +27,6 @@ import java.util.Arrays; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JOptionPane; - -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.ListSelectionModel; import javax.swing.SwingWorker; import org.netbeans.api.progress.ProgressHandle; @@ -37,7 +34,6 @@ import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.explorer.ExplorerManager; -import org.openide.explorer.view.IconView; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; @@ -45,9 +41,10 @@ import org.openide.nodes.NodeEvent; import org.openide.nodes.NodeListener; import org.openide.nodes.NodeMemberEvent; import org.openide.nodes.NodeReorderEvent; -import org.openide.util.Exceptions; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -63,18 +60,18 @@ import org.sleuthkit.datamodel.TskCoreException; // service provider when DataResultViewers can be made compatible with node // multi-selection actions. //@ServiceProvider(service = DataResultViewer.class) - final class DataResultViewerThumbnail extends AbstractDataResultViewer { +final class DataResultViewerThumbnail extends AbstractDataResultViewer { private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); //flag to keep track if images are being loaded private int curPage; private int totalPages; private int curPageImages; - private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; + private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; private final PageUpdater pageUpdater = new PageUpdater(); /** - * Creates a DataResultViewerThumbnail object that is compatible with node + * Creates a DataResultViewerThumbnail object that is compatible with node * multiple selection actions. */ public DataResultViewerThumbnail(ExplorerManager explorerManager) { @@ -83,24 +80,24 @@ import org.sleuthkit.datamodel.TskCoreException; } /** - * Creates a DataResultViewerThumbnail object that is NOT compatible with + * Creates a DataResultViewerThumbnail object that is NOT compatible with * node multiple selection actions. */ public DataResultViewerThumbnail() { initialize(); - } - + } + private void initialize() { initComponents(); - ((IconView) thumbnailScrollPanel).setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); curPage = -1; totalPages = 0; curPageImages = 0; - } - + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -110,7 +107,6 @@ import org.sleuthkit.datamodel.TskCoreException; // //GEN-BEGIN:initComponents private void initComponents() { - thumbnailScrollPanel = new IconView(); pageLabel = new javax.swing.JLabel(); pagesLabel = new javax.swing.JLabel(); pagePrevButton = new javax.swing.JButton(); @@ -122,32 +118,31 @@ import org.sleuthkit.datamodel.TskCoreException; goToPageLabel = new javax.swing.JLabel(); goToPageField = new javax.swing.JTextField(); thumbnailSizeComboBox = new javax.swing.JComboBox<>(); - - thumbnailScrollPanel.setPreferredSize(new java.awt.Dimension(582, 348)); + iconView = new org.openide.explorer.view.IconView(); pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageLabel.text")); // NOI18N pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pagesLabel.text")); // NOI18N - pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N NON-NLS + pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pagePrevButton.text")); // NOI18N - pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N NON-NLS + pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23)); - pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N NON-NLS + pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N pagePrevButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { pagePrevButtonActionPerformed(evt); } }); - pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N NON-NLS + pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageNextButton.text")); // NOI18N - pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N NON-NLS + pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23)); pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23)); - pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N NON-NLS + pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N pageNextButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { pageNextButtonActionPerformed(evt); @@ -171,10 +166,7 @@ import org.sleuthkit.datamodel.TskCoreException; } }); - thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.smallThumbnails"), - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.mediumThumbnails"), - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.largeThumbnails") })); + thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Small Thumbnails", "Medium Thumbnails", "Large Thumbnails" })); thumbnailSizeComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { thumbnailSizeComboBoxActionPerformed(evt); @@ -185,32 +177,37 @@ import org.sleuthkit.datamodel.TskCoreException; this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 642, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(filePathLabel) .addGroup(layout.createSequentialGroup() - .addComponent(pageLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(pagesLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(filePathLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(pageLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(pagesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(goToPageLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(12, 12, 12) + .addComponent(imagesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(layout.createSequentialGroup() .addGap(0, 0, 0) - .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(goToPageLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(12, 12, 12) - .addComponent(imagesLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 563, Short.MAX_VALUE) + .addGap(0, 0, 0))) + .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -228,8 +225,8 @@ import org.sleuthkit.datamodel.TskCoreException; .addComponent(goToPageLabel) .addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addGap(0, 0, 0) - .addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 330, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(filePathLabel)) ); @@ -248,28 +245,28 @@ import org.sleuthkit.datamodel.TskCoreException; }//GEN-LAST:event_goToPageFieldActionPerformed private void thumbnailSizeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_thumbnailSizeComboBoxActionPerformed - + iconSize = ImageUtils.ICON_SIZE_MEDIUM; //default size - switch(thumbnailSizeComboBox.getSelectedIndex()) { + switch (thumbnailSizeComboBox.getSelectedIndex()) { case 0: iconSize = ImageUtils.ICON_SIZE_SMALL; break; case 2: iconSize = ImageUtils.ICON_SIZE_LARGE; break; - } + } Node root = em.getRootContext(); - for (Children c : Arrays.asList(root.getChildren()) ) { - ((ThumbnailViewChildren)c).setIconSize(iconSize); + for (Children c : Arrays.asList(root.getChildren())) { + ((ThumbnailViewChildren) c).setIconSize(iconSize); } - + for (Node page : root.getChildren().getNodes()) { for (Node node : page.getChildren().getNodes()) { - ((ThumbnailViewNode)node).setIconSize(iconSize); + ((ThumbnailViewNode) node).setIconSize(iconSize); } - } - + } + // Temporarily set the explored context to the root, instead of a child node. // This is a workaround hack to convince org.openide.explorer.ExplorerManager to // update even though the new and old Node values are identical. This in turn @@ -283,6 +280,7 @@ import org.sleuthkit.datamodel.TskCoreException; private javax.swing.JLabel filePathLabel; private javax.swing.JTextField goToPageField; private javax.swing.JLabel goToPageLabel; + private org.openide.explorer.view.IconView iconView; private javax.swing.JLabel imagesLabel; private javax.swing.JLabel imagesRangeLabel; private javax.swing.JLabel pageLabel; @@ -290,7 +288,6 @@ import org.sleuthkit.datamodel.TskCoreException; private javax.swing.JLabel pageNumLabel; private javax.swing.JButton pagePrevButton; private javax.swing.JLabel pagesLabel; - private javax.swing.JScrollPane thumbnailScrollPanel; private javax.swing.JComboBox thumbnailSizeComboBox; // End of variables declaration//GEN-END:variables @@ -299,7 +296,14 @@ import org.sleuthkit.datamodel.TskCoreException; if (selectedNode == null) { return false; } - return true; + + Children ch = selectedNode.getChildren(); + for (Node n : ch.getNodes()) { + if (ThumbnailViewChildren.isSupported(n)) { + return true; + } + } + return false; } @Override @@ -309,7 +313,7 @@ import org.sleuthkit.datamodel.TskCoreException; try { if (givenNode != null) { ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, iconSize); - + final Node root = new AbstractNode(childNode); pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); @@ -318,14 +322,13 @@ import org.sleuthkit.datamodel.TskCoreException; Node emptyNode = new AbstractNode(Children.LEAF); em.setRootContext(emptyNode); // make empty node - IconView iv = ((IconView) this.thumbnailScrollPanel); - iv.setBackground(Color.BLACK); + iconView.setBackground(Color.BLACK); } } finally { this.setCursor(null); } } - + @Override public String getTitle() { return NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.title"); @@ -348,8 +351,8 @@ import org.sleuthkit.datamodel.TskCoreException; @Override public void clearComponent() { - this.thumbnailScrollPanel.removeAll(); - this.thumbnailScrollPanel = null; + this.iconView.removeAll(); + this.iconView = null; super.clearComponent(); } @@ -369,28 +372,27 @@ import org.sleuthkit.datamodel.TskCoreException; switchPage(); } } - + private void goToPage(String pageNumText) { int newPage; try { newPage = Integer.parseInt(pageNumText); - } - catch (NumberFormatException e) { + } catch (NumberFormatException e) { //ignore input return; } - + if (newPage > totalPages || newPage < 1) { JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "DataResultViewerThumbnail.goToPageTextField.msgDlg", - totalPages), - NbBundle.getMessage(this.getClass(), - "DataResultViewerThumbnail.goToPageTextField.err"), - JOptionPane.WARNING_MESSAGE); + NbBundle.getMessage(this.getClass(), + "DataResultViewerThumbnail.goToPageTextField.msgDlg", + totalPages), + NbBundle.getMessage(this.getClass(), + "DataResultViewerThumbnail.goToPageTextField.err"), + JOptionPane.WARNING_MESSAGE); return; } - + curPage = newPage; switchPage(); } @@ -418,7 +420,7 @@ import org.sleuthkit.datamodel.TskCoreException; progress.start(); progress.switchToIndeterminate(); Node root = em.getRootContext(); - Node pageNode = root.getChildren().getNodeAt(curPage - 1); + Node pageNode = root.getChildren().getNodeAt(curPage - 1); em.setExploredContext(pageNode); curPageImages = pageNode.getChildren().getNodesCount(); return null; @@ -428,16 +430,16 @@ import org.sleuthkit.datamodel.TskCoreException; protected void done() { progress.finish(); setCursor(null); - updateControls(); + updateControls(); // see if any exceptions were thrown try { get(); } catch (InterruptedException | ExecutionException ex) { - NotifyDescriptor d = - new NotifyDescriptor.Message( - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", - ex.getMessage()), - NotifyDescriptor.ERROR_MESSAGE); + NotifyDescriptor d + = new NotifyDescriptor.Message( + NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", + ex.getMessage()), + NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(d); logger.log(Level.SEVERE, "Error making thumbnails: " + ex.getMessage()); //NON-NLS } // catch and ignore if we were cancelled @@ -458,14 +460,14 @@ import org.sleuthkit.datamodel.TskCoreException; } else { pageNumLabel.setText( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", - Integer.toString(curPage), Integer.toString(totalPages))); + Integer.toString(curPage), Integer.toString(totalPages))); final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; imagesRangeLabel.setText(imagesFrom + "-" + imagesTo); pageNextButton.setEnabled(!(curPage == totalPages)); pagePrevButton.setEnabled(!(curPage == 1)); - goToPageField.setEnabled(totalPages>1); + goToPageField.setEnabled(totalPages > 1); } @@ -534,7 +536,6 @@ import org.sleuthkit.datamodel.TskCoreException; em.setExploredContext(pageNode); } - updateControls(); } @@ -554,8 +555,9 @@ import org.sleuthkit.datamodel.TskCoreException; public void nodeDestroyed(NodeEvent ne) { } } - + private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { + @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { @@ -566,26 +568,23 @@ import org.sleuthkit.datamodel.TskCoreException; AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class); if (af == null) { filePathLabel.setText(""); - } - else { + } else { try { String uPath = af.getUniquePath(); filePathLabel.setText(uPath); filePathLabel.setToolTipText(uPath); - } - catch (TskCoreException e){ + } catch (TskCoreException e) { logger.log(Level.WARNING, "Could not get unique path for content: {0}", af.getName()); //NON-NLS } - } + } + } else { + filePathLabel.setText(""); } - else { - filePathLabel.setText(""); - } - } - finally { + } finally { setCursor(null); } - } + } } - } + + } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.form index 5143a19b43..d618556924 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.form @@ -16,8 +16,35 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java index 728749ccf6..16dbc8df71 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,9 @@ */ package org.sleuthkit.autopsy.corecomponents; +import com.google.common.io.Files; import java.awt.Dimension; +import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.util.Arrays; @@ -26,11 +28,8 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.logging.Level; import javafx.application.Platform; -import javafx.beans.InvalidationListener; import javafx.beans.Observable; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.embed.swing.JFXPanel; +import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; @@ -55,17 +54,15 @@ import static javafx.scene.media.MediaPlayer.Status.STOPPED; import javafx.scene.media.MediaView; import javafx.util.Duration; import javax.swing.JPanel; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; -import org.openide.util.Cancellable; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.VideoUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -78,58 +75,37 @@ import org.sleuthkit.datamodel.TskData; @ServiceProvider(service = FrameCapture.class) }) public class FXVideoPanel extends MediaViewVideoPanel { - + // Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html // for Javafx supported formats private static final String[] EXTENSIONS = new String[]{".m4v", ".fxm", ".flv", ".m3u8", ".mp4", ".aif", ".aiff", ".mp3", "m4a", ".wav"}; //NON-NLS private static final List MIMETYPES = Arrays.asList("audio/x-aiff", "video/x-javafx", "video/x-flv", "application/vnd.apple.mpegurl", " audio/mpegurl", "audio/mpeg", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS - private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); + private static final Logger logger = Logger.getLogger(FXVideoPanel.class.getName()); private boolean fxInited = false; - // FX Components private MediaPane mediaPane; - // Current media content representations private AbstractFile currentFile; - // FX UI Components - private JFXPanel videoComponent; - - /** - * Creates new form MediaViewVideoPanel - */ public FXVideoPanel() { fxInited = Installer.isJavaFxInited(); initComponents(); if (fxInited) { - setupFx(); + Platform.runLater(() -> { + + mediaPane = new MediaPane(); + Scene fxScene = new Scene(mediaPane); + jFXPanel.setScene(fxScene); + }); } } + @Deprecated public JPanel getVideoPanel() { return this; } - private void setupFx() { - Platform.runLater(new Runnable() { - @Override - public void run() { - videoComponent = new JFXPanel(); - mediaPane = new MediaPane(); - Scene fxScene = new Scene(mediaPane); - videoComponent.setScene(fxScene); - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - add(videoComponent); - } - }); - } - }); - } - @Override void setupVideo(final AbstractFile file, final Dimension dims) { if (file.equals(currentFile)) { @@ -148,50 +124,33 @@ public class FXVideoPanel extends MediaViewVideoPanel { removeAll(); return; } + mediaPane.setFit(dims); String path = ""; try { path = file.getUniquePath(); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Cannot get unique path of video file"); //NON-NLS + logger.log(Level.SEVERE, "Cannot get unique path of video file", ex); //NON-NLS } mediaPane.setInfoLabelText(path); mediaPane.setInfoLabelToolTipText(path); - ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); - em.execute(); + final File tempFile = VideoUtils.getTempVideoFile(currentFile); + + new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start(); - mediaPane.setFit(dims); } @Override void reset() { - Platform.runLater(new Runnable() { - @Override - public void run() { - if (mediaPane != null) { - mediaPane.reset(); - } + Platform.runLater(() -> { + if (mediaPane != null) { + mediaPane.reset(); } }); currentFile = null; } - private java.io.File getJFile(AbstractFile file) { - // Get the temp folder path of the case - String tempPath = Case.getCurrentCase().getTempDirectory(); - String name = file.getName(); - int extStart = name.lastIndexOf("."); - String ext = ""; - if (extStart != -1) { - ext = name.substring(extStart, name.length()).toLowerCase(); - } - tempPath = tempPath + java.io.File.separator + file.getId() + ext; - - java.io.File tempFile = new java.io.File(tempPath); - return tempFile; - } - /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -201,10 +160,34 @@ public class FXVideoPanel extends MediaViewVideoPanel { // //GEN-BEGIN:initComponents private void initComponents() { + jFXPanel = new javafx.embed.swing.JFXPanel(); + setBackground(new java.awt.Color(0, 0, 0)); - setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.LINE_AXIS)); + + javax.swing.GroupLayout jFXPanelLayout = new javax.swing.GroupLayout(jFXPanel); + jFXPanel.setLayout(jFXPanelLayout); + jFXPanelLayout.setHorizontalGroup( + jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 400, Short.MAX_VALUE) + ); + jFXPanelLayout.setVerticalGroup( + jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 300, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private javafx.embed.swing.JFXPanel jFXPanel; // End of variables declaration//GEN-END:variables @Override @@ -212,134 +195,32 @@ public class FXVideoPanel extends MediaViewVideoPanel { return fxInited; } - /** - * Thread that extracts Media from a Sleuthkit file representation to a - * Java file representation that the Media Player can take as input. - */ - private class ExtractMedia extends SwingWorker { - - private ProgressHandle progress; - - boolean success = false; - - private AbstractFile sFile; - - private java.io.File jFile; - - private long extractedBytes; - - ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { - this.sFile = sFile; - this.jFile = jFile; - } - - public long getExtractedBytes() { - return extractedBytes; - } - - /** - * Get the URI of the media file. - * - * @return the URI of the media file. - */ - public String getMediaUri() { - return Paths.get(jFile.getAbsolutePath()).toUri().toString(); - } - - @Override - protected Object doInBackground() throws Exception { - success = false; - progress = ProgressHandleFactory.createHandle( - NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingFile", sFile.getName()), - new Cancellable() { - @Override - public boolean cancel() { - return ExtractMedia.this.cancel(true); - } - }); - mediaPane.setProgressLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.buffering")); - progress.start(); - progress.switchToDeterminate(100); - try { - extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS - } - logger.log(Level.INFO, "Done buffering: " + jFile.getName()); //NON-NLS - success = true; - return null; - } - - /* clean up or start the worker threads */ - @Override - protected void done() { - mediaPane.setProgressLabelText(""); - try { - super.get(); //block and get all exceptions thrown while doInBackground() - } catch (CancellationException ex) { - logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS - mediaPane.setProgressLabelText( - NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingCancelled")); - } catch (InterruptedException ex) { - logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS - mediaPane.setProgressLabelText( - NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingInterrupted")); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS - mediaPane.setProgressLabelText( - NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.errorWritingVideoToDisk")); - } finally { - progress.finish(); - if (!this.isCancelled()) { - logger.log(Level.INFO, "ExtractMedia in done: " + jFile.getName()); //NON-NLS - try { - Platform.runLater(new Runnable() { - @Override - public void run() { - mediaPane.prepareMedia(getMediaUri()); - } - }); - } catch (MediaException e) { - logger.log(Level.WARNING, "something went wrong with javafx", e); //NON-NLS - reset(); - mediaPane.setInfoLabelText(e.getMessage()); - return; - } - } - } - } - } - - /** - * The JavaFX Component that contains the Media and it's Controls. - * - */ private class MediaPane extends BorderPane { private MediaPlayer mediaPlayer; - private MediaView mediaView; + private final MediaView mediaView; /** The Duration of the media. * */ private Duration duration; /** The container for the media controls. * */ - private HBox mediaTools; + private final HBox mediaTools; /** The container for the media video output. * */ - private HBox mediaViewPane; + private final HBox mediaViewPane; - private VBox controlPanel; + private final VBox controlPanel; - private Slider progressSlider; + private final Slider progressSlider; - private Button pauseButton; + private final Button pauseButton; - private Button stopButton; + private final Button stopButton; - private Label progressLabel; + private final Label progressLabel; - private Label infoLabel; + private final Label infoLabel; private int totalHours; @@ -347,22 +228,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { private int totalSeconds; - private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS - - /** The EventHandler for MediaPlayer.onReady(). * */ - private final ReadyListener READY_LISTENER = new ReadyListener(); - - /** The EventHandler for MediaPlayer.onEndOfMedia(). * */ - private final EndOfMediaListener END_LISTENER = new EndOfMediaListener(); - - /** The EventHandler for the CurrentTime property of the MediaPlayer. * */ - private final TimeListener TIME_LISTENER = new TimeListener(); - - /** The EventHandler for MediaPlayer.onPause and MediaPlayer.onStop. * */ - private final NotPlayListener NOT_PLAY_LISTENER = new NotPlayListener(); - - /** The EventHandler for MediaPlayer.onPlay. * */ - private final PlayListener PLAY_LISTENER = new PlayListener(); + private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS private static final String PLAY_TEXT = "►"; @@ -409,22 +275,6 @@ public class FXVideoPanel extends MediaViewVideoPanel { setProgressActionListeners(); } - /** - * Setup the MediaPane for media playback. Run on the JavaFx Thread. - * - * - * @param mediaUri the URI of the media - */ - public void prepareMedia(String mediaUri) { - try { - mediaPlayer = createMediaPlayer(mediaUri); - mediaView.setMediaPlayer(mediaPlayer); - } catch (MediaException ex) { - this.setProgressLabelText(""); - this.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat")); - } - } - /** * Reset this MediaPane. * @@ -447,12 +297,9 @@ public class FXVideoPanel extends MediaViewVideoPanel { * @param text */ public void setInfoLabelText(final String text) { - logger.log(Level.INFO, "Setting Info Label Text: " + text); //NON-NLS - Platform.runLater(new Runnable() { - @Override - public void run() { - infoLabel.setText(text); - } + logger.log(Level.INFO, "Setting Info Label Text: {0}", text); //NON-NLS + Platform.runLater(() -> { + infoLabel.setText(text); }); } @@ -462,14 +309,11 @@ public class FXVideoPanel extends MediaViewVideoPanel { * @param dims the current dimensions of the DataContentViewer */ public void setFit(final Dimension dims) { - Platform.runLater(new Runnable() { - @Override - public void run() { - setPrefSize(dims.getWidth(), dims.getHeight()); - // Set the Video output to fit the size allocated for it. give an - // extra few px to ensure the info label will be shown - mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight()); - } + Platform.runLater(() -> { + setPrefSize(dims.getWidth(), dims.getHeight()); + // Set the Video output to fit the size allocated for it. give an + // extra few px to ensure the info label will be shown + mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight()); }); } @@ -498,7 +342,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { mediaPlayer.play(); break; default: - logger.log(Level.INFO, "MediaPlayer in unexpected state: " + status.toString()); //NON-NLS + logger.log(Level.INFO, "MediaPlayer in unexpected state: {0}", status.toString()); //NON-NLS // If the MediaPlayer is in an unexpected state, stop playback. mediaPlayer.stop(); setInfoLabelText(NbBundle.getMessage(this.getClass(), @@ -508,27 +352,21 @@ public class FXVideoPanel extends MediaViewVideoPanel { } }); - stopButton.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent e) { - if (mediaPlayer == null) { - return; - } - - mediaPlayer.stop(); + stopButton.setOnAction((ActionEvent e) -> { + if (mediaPlayer == null) { + return; } + + mediaPlayer.stop(); }); - progressSlider.valueProperty().addListener(new InvalidationListener() { - @Override - public void invalidated(Observable o) { - if (mediaPlayer == null) { - return; - } + progressSlider.valueProperty().addListener((Observable o) -> { + if (mediaPlayer == null) { + return; + } - if (progressSlider.isValueChanging()) { - mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0)); - } + if (progressSlider.isValueChanging()) { + mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0)); } }); } @@ -557,13 +395,21 @@ public class FXVideoPanel extends MediaViewVideoPanel { Media media = new Media(mediaUri); MediaPlayer player = new MediaPlayer(media); - player.setOnReady(READY_LISTENER); - player.setOnPaused(NOT_PLAY_LISTENER); - player.setOnStopped(NOT_PLAY_LISTENER); - player.setOnPlaying(PLAY_LISTENER); - player.setOnEndOfMedia(END_LISTENER); + player.setOnReady(new ReadyListener()); + final Runnable pauseListener = () -> { + pauseButton.setText(PLAY_TEXT); + }; + player.setOnPaused(pauseListener); + player.setOnStopped(pauseListener); + player.setOnPlaying(() -> { + pauseButton.setText(PAUSE_TEXT); + }); + player.setOnEndOfMedia(new EndOfMediaListener()); - player.currentTimeProperty().addListener(TIME_LISTENER); + player.currentTimeProperty().addListener((observable, oldTime, newTime) -> { + updateSlider(newTime); + updateTime(newTime); + }); return player; } @@ -614,31 +460,16 @@ public class FXVideoPanel extends MediaViewVideoPanel { elapsedSeconds = (int) secondsElapsed; String durationStr = String.format(durationFormat, - elapsedHours, elapsedMinutes, elapsedSeconds, - totalHours, totalMinutes, totalSeconds); - setProgressLabelText(durationStr); - } - - /** - * Update the progress label to show the text. - * - * @param text - */ - private void setProgressLabelText(final String text) { - Platform.runLater(new Runnable() { - @Override - public void run() { - progressLabel.setText(text); - } + elapsedHours, elapsedMinutes, elapsedSeconds, + totalHours, totalMinutes, totalSeconds); + Platform.runLater(() -> { + progressLabel.setText(durationStr); }); } private void setInfoLabelToolTipText(final String text) { - Platform.runLater(new Runnable() { - @Override - public void run() { - infoLabel.setTooltip(new Tooltip(text)); - } + Platform.runLater(() -> { + infoLabel.setTooltip(new Tooltip(text)); }); } @@ -654,7 +485,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { if (mediaPlayer == null) { return; } - + duration = mediaPlayer.getMedia().getDuration(); long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis(); @@ -692,38 +523,105 @@ public class FXVideoPanel extends MediaViewVideoPanel { } /** - * Responds to changes in the MediaPlayer currentTime property. - * - * Updates the progress slider and label with the current Time. + * Thread that extracts Media from a Sleuthkit file representation to a + * Java file representation that the Media Player can take as input. */ - private class TimeListener implements ChangeListener { + private class ExtractMedia extends Task { - @Override - public void changed(ObservableValue observable, Duration oldValue, Duration newValue) { - updateSlider(newValue); - updateTime(newValue); + private ProgressHandle progress; + + private final AbstractFile sourceFile; + + private final java.io.File tempFile; + + ExtractMedia(AbstractFile sFile, java.io.File jFile) { + this.sourceFile = sFile; + this.tempFile = jFile; } - } - /** - * Triggered when MediaPlayer State changes to PAUSED or Stopped. - */ - private class NotPlayListener implements Runnable { - - @Override - public void run() { - pauseButton.setText(PLAY_TEXT); + /** + * Get the URI of the media file. + * + * @return the URI of the media file. + */ + public String getMediaUri() { + return Paths.get(tempFile.getAbsolutePath()).toUri().toString(); } - } - - /** - * Triggered when MediaPlayer State changes to PLAYING. - */ - private class PlayListener implements Runnable { @Override - public void run() { - pauseButton.setText(PAUSE_TEXT); + protected Long call() throws Exception { + if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) { + progress = ProgressHandleFactory.createHandle( + NbBundle.getMessage(this.getClass(), + "FXVideoPanel.progress.bufferingFile", + sourceFile.getName() + ), + () -> ExtractMedia.this.cancel(true)); + + Platform.runLater(() -> { + progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.buffering")); + }); + + progress.start(100); + try { + Files.createParentDirs(tempFile); + return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS + return 0L; + } finally { + logger.log(Level.INFO, "Done buffering: {0}", tempFile.getName()); //NON-NLS + } + } + return 0L; + } + + @Override + protected void failed() { + super.failed(); + onDone(); + } + + @Override + protected void succeeded() { + super.succeeded(); + onDone(); + } + + @Override + protected void cancelled() { + super.cancelled(); + onDone(); + } + + private void onDone() { + progressLabel.setText(""); + try { + super.get(); //block and get all exceptions thrown while doInBackground() + } catch (CancellationException ex) { + logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS + progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingCancelled")); + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS + progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingInterrupted")); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS + progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.errorWritingVideoToDisk")); + } finally { + if (null != progress) { + progress.finish(); + } + if (!this.isCancelled()) { + logger.log(Level.INFO, "ExtractMedia is done: {0}", tempFile.getName()); //NON-NLS + try { + mediaPane.mediaPlayer = mediaPane.createMediaPlayer(getMediaUri()); + mediaView.setMediaPlayer(mediaPane.mediaPlayer); + } catch (MediaException ex) { + progressLabel.setText(""); + mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat")); + } + } + } } } } @@ -739,114 +637,13 @@ public class FXVideoPanel extends MediaViewVideoPanel { */ @Override public List captureFrames(java.io.File file, int numFrames) throws Exception { -// -// try { -// List frames = new ArrayList<>(); -// -// FrameCapturer fc = new FrameCapturer(file); -// logger.log(Level.INFO, "Fc is null? " + (fc == null)); -// frames = fc.getFrames(numFrames); -// -// return frames; -// } -// catch (NullPointerException e) { -// e.printStackTrace(); -// return null; -// } + //What is/was the point of this method /interface. return null; } -// private class FrameCapturer { -// -// private MediaPlayer mediaPlayer; -// private JFXPanel panel; -// private boolean isReady = false; -// -// FrameCapturer(java.io.File file) { -// initFx(file); -// } -// -// boolean isReady() { -// return isReady; -// } -// -// private void initFx(final java.io.File file) { -// Platform.runAndWait(new Runnable() { -// @Override -// public void run() { -// logger.log(Level.INFO, "In initFX."); -// // Create Media Player with no video output -// Media media = new Media(Paths.get(file.getAbsolutePath()).toUri().toString()); -// mediaPlayer = new MediaPlayer(media); -// MediaView mediaView = new MediaView(mediaPlayer); -// mediaView.setStyle("-fx-background-color: black"); -// Pane mediaViewPane = new Pane(); -// mediaViewPane.getChildren().add(mediaView); -// Scene scene = new Scene(mediaViewPane); -// panel = new JFXPanel(); -// panel.setScene(scene); -// isReady = true; -// } -// }); -// } -// -// List getFrames(int numFrames) { -// logger.log(Level.INFO, "in get frames"); -// List frames = new ArrayList(0); -// -// if (mediaPlayer.getStatus() != Status.READY) { -// try { -// Thread.sleep(500); -// } catch (InterruptedException e) { -// return frames; -// } -// } -// -// // get the duration of the video -// long myDurationMillis = (long) mediaPlayer.getMedia().getDuration().toMillis(); -// if (myDurationMillis <= 0) { -// return frames; -// } -// -// // calculate the frame interval -// int numFramesToGet = numFrames; -// long frameInterval = (myDurationMillis - INTER_FRAME_PERIOD_MS) / numFrames; -// if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { -// numFramesToGet = 1; -// } -// -// final Object frameLock = new Object(); -// BufferedImage frame; -// final int width = (int) panel.getSize().getWidth(); -// final int height = (int) panel.getSize().getHeight(); -// // for each timeStamp, grap a frame -// for (int i = 0; i < numFramesToGet; ++i) { -// frame = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); -// logger.log(Level.INFO, "Grabbing a frame..."); -// final long timeStamp = i * frameInterval + INTER_FRAME_PERIOD_MS; -// -// // Platform.runLater(new Runnable() { -// // @Override -// // public void run() { -// // synchronized (frameLock) { -// logger.log(Level.INFO, "seeking."); -// mediaPlayer.seek(new Duration(timeStamp)); -// // } -// // } -// // }); -// -// synchronized (frameLock) { -// panel.paint(frame.createGraphics()); -// logger.log(Level.INFO, "Adding image to frames"); -// } -// frames.add(new VideoFrame(frame, timeStamp)); -// } -// return frames; -// } -// } @Override public String[] getExtensions() { - return EXTENSIONS; + return EXTENSIONS.clone(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java index db5f97b40f..9039f79abd 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Dimension; import java.awt.Image; import java.awt.image.BufferedImage; +import java.io.File; import java.io.IOException; import java.nio.IntBuffer; import java.util.ArrayList; @@ -41,7 +42,6 @@ import javax.swing.JSlider; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import org.gstreamer.ClockTime; import org.gstreamer.Gst; import org.gstreamer.GstException; @@ -52,26 +52,25 @@ import org.gstreamer.elements.RGBDataSink; import org.gstreamer.swing.VideoComponent; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; -import org.openide.util.Cancellable; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.VideoUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; - @ServiceProviders(value = { @ServiceProvider(service = FrameCapture.class) }) public class GstVideoPanel extends MediaViewVideoPanel { - private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS + + private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS private static final List MIMETYPES = Arrays.asList("video/quicktime", "audio/mpeg", "audio/x-mpeg", "video/mpeg", "video/x-mpeg", "audio/mpeg3", "audio/x-mpeg-3", "video/x-flv", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS - + private static final Logger logger = Logger.getLogger(GstVideoPanel.class.getName()); private boolean gstInited; private static final long MIN_FRAME_INTERVAL_MILLIS = 500; @@ -87,7 +86,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player private AbstractFile currentFile; private final Set badVideoFiles = Collections.synchronizedSet(new HashSet()); - + /** * Creates new form MediaViewVideoPanel */ @@ -129,30 +128,27 @@ public class GstVideoPanel extends MediaViewVideoPanel { progressSlider.setEnabled(false); // disable slider; enable after user plays vid progressSlider.setValue(0); - progressSlider.addChangeListener(new ChangeListener() { + progressSlider.addChangeListener((ChangeEvent e) -> { /** * Should always try to synchronize any call to * progressSlider.setValue() to avoid a different thread * changing playbin while stateChanged() is processing */ - @Override - public void stateChanged(ChangeEvent e) { - int time = progressSlider.getValue(); - synchronized (playbinLock) { - if (gstPlaybin2 != null && !autoTracking) { - State orig = gstPlaybin2.getState(); - if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - gstPlaybin2.setState(orig); + int time = progressSlider.getValue(); + synchronized (playbinLock) { + if (gstPlaybin2 != null && !autoTracking) { + State orig = gstPlaybin2.getState(); + if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; } + if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + gstPlaybin2.setState(orig); } } }); @@ -195,7 +191,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { progressSlider.setEnabled(false); return; } - + String path = ""; try { path = file.getUniquePath(); @@ -207,7 +203,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { pauseButton.setEnabled(true); progressSlider.setEnabled(true); - java.io.File ioFile = getJFile(file); + java.io.File ioFile = VideoUtils.getTempVideoFile(file); gstVideoComponent = new VideoComponent(); synchronized (playbinLock) { @@ -219,14 +215,13 @@ public class GstVideoPanel extends MediaViewVideoPanel { videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.add(gstVideoComponent); videoPanel.setVisible(true); gstPlaybin2.setInputFile(ioFile); - + if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) { logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); @@ -239,11 +234,8 @@ public class GstVideoPanel extends MediaViewVideoPanel { void reset() { // reset the progress label text on the event dispatch thread - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressLabel.setText(""); - } + SwingUtilities.invokeLater(() -> { + progressLabel.setText(""); }); if (!isInited()) { @@ -281,41 +273,27 @@ public class GstVideoPanel extends MediaViewVideoPanel { currentFile = null; } - private java.io.File getJFile(AbstractFile file) { - // Get the temp folder path of the case - String tempPath = Case.getCurrentCase().getTempDirectory(); - String name = file.getName(); - int extStart = name.lastIndexOf("."); - String ext = ""; - if (extStart != -1) { - ext = name.substring(extStart, name.length()).toLowerCase(); - } - tempPath = tempPath + java.io.File.separator + file.getId() + ext; - - java.io.File tempFile = new java.io.File(tempPath); - return tempFile; - } - /** - * @param file a video file from which to capture frames + * @param file a video file from which to capture frames * @param numFrames the number of frames to capture. These frames will be - * captured at successive intervals given by durationOfVideo/numFrames. If - * this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one - * frame will be captured and returned. + * captured at successive intervals given by durationOfVideo/numFrames. If + * this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one + * frame will be captured and returned. + * * @return a List of VideoFrames representing the captured frames. */ @Override public List captureFrames(java.io.File file, int numFrames) throws Exception { List frames = new ArrayList<>(); - + Object lock = new Object(); FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock); - + if (!isInited()) { return frames; } - + // throw exception if this file is known to be problematic if (badVideoFiles.contains(file.getName())) { throw new Exception( @@ -374,7 +352,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { if (!playbin.seek(timeStamp, unit)) { logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); //NON-NLS } - + ret = playbin.play(); if (ret == StateChangeReturn.FAILURE) { // add this file to the set of known bad ones @@ -384,7 +362,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { } // wait for FrameCaptureRGBListener to finish - synchronized(lock) { + synchronized (lock) { try { lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS); } catch (InterruptedException e) { @@ -400,7 +378,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { throw new Exception( NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemStopCaptFrame.msg")); } - + if (image == null) { logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName()); //NON-NLS badVideoFiles.add(file.getName()); @@ -412,13 +390,13 @@ public class GstVideoPanel extends MediaViewVideoPanel { return frames; } - + private class FrameCaptureRGBListener implements RGBDataSink.Listener { public FrameCaptureRGBListener(Object waiter) { this.waiter = waiter; } - + private BufferedImage bi; private final Object waiter; @@ -438,7 +416,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { return image; } } - + } /** @@ -460,12 +438,12 @@ public class GstVideoPanel extends MediaViewVideoPanel { javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); videoPanel.setLayout(videoPanelLayout); videoPanelLayout.setHorizontalGroup( - videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) ); videoPanelLayout.setVerticalGroup( - videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 231, Short.MAX_VALUE) + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 231, Short.MAX_VALUE) ); org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(GstVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N @@ -482,48 +460,48 @@ public class GstVideoPanel extends MediaViewVideoPanel { javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); controlPanel.setLayout(controlPanelLayout); controlPanelLayout.setHorizontalGroup( - controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createSequentialGroup() - .addContainerGap() - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createSequentialGroup() - .addGap(6, 6, 6) - .addComponent(infoLabel) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(controlPanelLayout.createSequentialGroup() - .addComponent(pauseButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressLabel) - .addContainerGap()))) + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createSequentialGroup() + .addGap(6, 6, 6) + .addComponent(infoLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(controlPanelLayout.createSequentialGroup() + .addComponent(pauseButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressLabel) + .addContainerGap()))) ); controlPanelLayout.setVerticalGroup( - controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createSequentialGroup() - .addContainerGap() - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(pauseButton) - .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(infoLabel) - .addContainerGap()) + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(pauseButton) + .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(infoLabel) + .addContainerGap()) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) ); }// @@ -557,9 +535,10 @@ public class GstVideoPanel extends MediaViewVideoPanel { return; } } else if (state.equals(State.READY)) { - ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); - em.execute(); - em.getExtractedBytes(); + final File tempVideoFile = VideoUtils.getTempVideoFile(currentFile); + + new ExtractMedia(currentFile, tempVideoFile).execute(); + } } }//GEN-LAST:event_pauseButtonActionPerformed @@ -572,14 +551,13 @@ public class GstVideoPanel extends MediaViewVideoPanel { private javax.swing.JSlider progressSlider; private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables - + private class VideoProgressWorker extends SwingWorker { - private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS + private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS private long millisElapsed = 0; private final long INTER_FRAME_PERIOD_MS = 20; private final long END_TIME_MARGIN_MS = 50; - private boolean hadError = false; private boolean isPlayBinReady() { synchronized (playbinLock) { @@ -612,9 +590,9 @@ public class GstVideoPanel extends MediaViewVideoPanel { /** * @return true while millisElapsed is greater than END_TIME_MARGIN_MS - * from durationMillis. This is used to indicate when the video has - * ended because for some videos the time elapsed never becomes equal to - * the reported duration of the video. + * from durationMillis. This is used to indicate when the video has + * ended because for some videos the time elapsed never becomes equal to + * the reported duration of the video. */ private boolean hasNotEnded() { return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; @@ -626,8 +604,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { // enable the slider progressSlider.setEnabled(true); - int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; - ClockTime pos = null; + ClockTime pos; while (hasNotEnded() && isPlayBinReady() && !isCancelled()) { synchronized (playbinLock) { @@ -637,11 +614,11 @@ public class GstVideoPanel extends MediaViewVideoPanel { // pick out the elapsed hours, minutes, seconds long secondsElapsed = millisElapsed / 1000; - elapsedHours = (int) secondsElapsed / 3600; + int elapsedHours = (int) secondsElapsed / 3600; secondsElapsed -= elapsedHours * 3600; - elapsedMinutes = (int) secondsElapsed / 60; + int elapsedMinutes = (int) secondsElapsed / 60; secondsElapsed -= elapsedMinutes * 60; - elapsedSeconds = (int) secondsElapsed; + int elapsedSeconds = (int) secondsElapsed; String durationStr = String.format(durationFormat, elapsedHours, elapsedMinutes, elapsedSeconds, @@ -666,8 +643,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { return null; } - - + @Override protected void done() { // see if any exceptions were thrown @@ -676,54 +652,39 @@ public class GstVideoPanel extends MediaViewVideoPanel { } catch (InterruptedException | ExecutionException ex) { logger.log(Level.WARNING, "Error updating video progress: " + ex.getMessage()); //NON-NLS infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.infoLabel.updateErr", - ex.getMessage())); + ex.getMessage())); + } // catch and ignore if we were cancelled + catch (java.util.concurrent.CancellationException ex) { } - // catch and ignore if we were cancelled - catch (java.util.concurrent.CancellationException ex ) { } } } //end class progress worker /* Thread that extracts and plays a file */ - private class ExtractMedia extends SwingWorker { + private class ExtractMedia extends SwingWorker { private ProgressHandle progress; - boolean success = false; - private AbstractFile sFile; - private java.io.File jFile; - private String duration; - private String position; - private long extractedBytes; + private final AbstractFile sourceFile; + private final java.io.File tempFile; - ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { - this.sFile = sFile; - this.jFile = jFile; - } - - public long getExtractedBytes() { - return extractedBytes; + ExtractMedia(AbstractFile sFile, java.io.File jFile) { + this.sourceFile = sFile; + this.tempFile = jFile; } @Override - protected Object doInBackground() throws Exception { - success = false; - progress = ProgressHandleFactory.createHandle( - NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sFile.getName()), - new Cancellable() { - @Override - public boolean cancel() { - return ExtractMedia.this.cancel(true); + protected Long doInBackground() throws Exception { + if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) { + progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> ExtractMedia.this.cancel(true)); + progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); + progress.start(100); + try { + return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS + return 0L; } - }); - progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); - progress.start(); - progress.switchToDeterminate(100); - try { - extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS } - success = true; - return null; + return 0L; } /* clean up or start the worker threads */ @@ -738,7 +699,9 @@ public class GstVideoPanel extends MediaViewVideoPanel { } catch (Exception ex) { logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS } finally { - progress.finish(); + if (progress != null) { + progress.finish(); + } if (!this.isCancelled()) { playMedia(); } @@ -746,11 +709,11 @@ public class GstVideoPanel extends MediaViewVideoPanel { } void playMedia() { - if (jFile == null || !jFile.exists()) { + if (tempFile == null || !tempFile.exists()) { progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr")); return; } - ClockTime dur = null; + ClockTime dur; synchronized (playbinLock) { // must play, then pause and get state to get duration. if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { @@ -766,7 +729,6 @@ public class GstVideoPanel extends MediaViewVideoPanel { gstPlaybin2.getState(); dur = gstPlaybin2.queryDuration(); } - duration = dur.toString(); durationMillis = dur.toMillis(); // pick out the total hours, minutes, seconds @@ -777,33 +739,31 @@ public class GstVideoPanel extends MediaViewVideoPanel { durationSeconds -= totalMinutes * 60; totalSeconds = (int) durationSeconds; - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressSlider.setMaximum((int) durationMillis); - progressSlider.setMinimum(0); + SwingUtilities.invokeLater(() -> { + progressSlider.setMaximum((int) durationMillis); + progressSlider.setMinimum(0); - synchronized (playbinLock) { - if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } + synchronized (playbinLock) { + if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); } - pauseButton.setText("||"); - videoProgressWorker = new VideoProgressWorker(); - videoProgressWorker.execute(); } + pauseButton.setText("||"); + videoProgressWorker = new VideoProgressWorker(); + videoProgressWorker.execute(); }); } } - + @Override public String[] getExtensions() { - return EXTENSIONS; + return EXTENSIONS.clone(); } @Override public List getMimeTypes() { return MIMETYPES; } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index c1a80e5566..2a6de774f9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -54,7 +54,7 @@ import org.sleuthkit.datamodel.ReadContentInputStream; * Image viewer part of the Media View layered pane. Uses JavaFX to display the * image. */ -public class MediaViewImagePanel extends JPanel { +public class MediaViewImagePanel extends JPanel implements DataContentViewerMedia.MediaViewPanel { private static final Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName()); @@ -64,20 +64,23 @@ public class MediaViewImagePanel extends JPanel { private ImageView fxImageView; private BorderPane borderpane; - private final Label errorLabel = new Label("Could not load image file into media view."); - private final Label tooLargeLabel = new Label("Could not load image file into media view (too large)."); - private final Label noReaderLabel = new Label("Image reader not found for file."); + private final Label errorLabel = new Label("Could not load file into media view."); + private final Label tooLargeLabel = new Label("Could not load file into media view (too large)."); + static { + ImageIO.scanForPlugins(); + + } /** * mime types we should be able to display. if the mimetype is unknown we * will fall back on extension and jpg/png header */ - static private final SortedSet supportedMimes = ImageUtils.getSupportedMimeTypes(); + static private final SortedSet supportedMimes = ImageUtils.getSupportedImageMimeTypes(); /** * extensions we should be able to display */ - static private final List supportedExtensions = ImageUtils.getSupportedExtensions().stream() + static private final List supportedExtensions = ImageUtils.getSupportedImageExtensions().stream() .map("."::concat) .collect(Collectors.toList()); @@ -157,7 +160,7 @@ public class MediaViewImagePanel extends JPanel { BufferedImage bufferedImage = ImageIO.read(inputStream); if (bufferedImage == null) { LOGGER.log(Level.WARNING, "Image reader not found for file: {0}", file.getName()); //NON-NLS - borderpane.setCenter(noReaderLabel); + borderpane.setCenter(errorLabel); } else { Image fxImage = SwingFXUtils.toFXImage(bufferedImage, null); if (fxImage.isError()) { @@ -191,10 +194,21 @@ public class MediaViewImagePanel extends JPanel { /** * @return supported mime types */ + @Override public List getMimeTypes() { return Collections.unmodifiableList(Lists.newArrayList(supportedMimes)); } + /** + * returns supported extensions (each starting with .) + * + * @return + */ + @Override + public List getExtensionsList() { + return getExtensions(); + } + /** * returns supported extensions (each starting with .) * @@ -204,6 +218,12 @@ public class MediaViewImagePanel extends JPanel { return Collections.unmodifiableList(supportedExtensions); } + @Override + public boolean isSupported(AbstractFile file) { + return DataContentViewerMedia.MediaViewPanel.super.isSupported(file) + || ImageUtils.hasImageFileHeader(file); + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -218,4 +238,5 @@ public class MediaViewImagePanel extends JPanel { }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index 05351a2959..2d41d27a27 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -21,30 +21,34 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Dimension; import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.TreeSet; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JPanel; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; /** * Video viewer part of the Media View layered pane. * Uses different engines depending on platform. */ -public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture { - +public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture, DataContentViewerMedia.MediaViewPanel { + + private static final Set AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS + private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); - + // 64 bit architectures private static final String[] ARCH64 = new String[]{"amd64", "x86_64"}; //NON-NLS NON-NLS - + // 32 bit architectures private static final String[] ARCH32 = new String[]{"x86"}; //NON-NLS - + /** - * Factory Method to create a MediaViewVideoPanel. - * + * Factory Method to create a MediaViewVideoPanel. + * * Implementation is dependent on the architecture of the JVM. - * + * * @return a MediaViewVideoPanel instance. */ public static MediaViewVideoPanel createVideoPanel() { @@ -56,10 +60,10 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture return getGstImpl(); } } - + /** * Is the JVM architecture 64 bit? - * + * * @return */ private static boolean is64BitJVM() { @@ -69,34 +73,34 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture /** * Get a GStreamer video player implementation. - * + * * @return a GstVideoPanel */ private static MediaViewVideoPanel getGstImpl() { return new GstVideoPanel(); } - /** + /** * Get a JavaFX video player implementation. - * + * * @return a FXVideoPanel */ private static MediaViewVideoPanel getFXImpl() { return new FXVideoPanel(); } - + /** * Has this MediaViewVideoPanel been initialized correctly? - * - * @return + * + * @return */ public abstract boolean isInited(); - + /** * Prepare this MediaViewVideoPanel to accept a different media file. */ abstract void reset(); - + /** * Initialize all the necessary vars to play a video/audio file. * @@ -104,13 +108,33 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture * @param dims dimension of the parent window */ abstract void setupVideo(final AbstractFile file, final Dimension dims); - + /** * Return the extensions supported by this video panel. + * + * @return */ abstract public String[] getExtensions(); + /** * Return the MimeTypes supported by this video panel. */ + @Override abstract public List getMimeTypes(); + + @Override + public List getExtensionsList() { + return Arrays.asList(getExtensions()); + } + + @Override + public boolean isSupported(AbstractFile file) { + String extension = file.getNameExtension(); + //TODO: is this what we want, to require both extension and mimetype support? + if (AUDIO_EXTENSIONS.contains("." + extension) || getExtensionsList().contains("." + extension)) { + return DataContentViewerMedia.MediaViewPanel.super.isSupported(file); //To change body of generated methods, choose Tools | Templates. + } + return false; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 8487310b67..4de945fb9e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +43,7 @@ class ThumbnailViewChildren extends Children.Keys { static final int IMAGES_PER_PAGE = 200; private Node parent; - private final HashMap> pages = new HashMap>(); + private final HashMap> pages = new HashMap<>(); private int totalImages = 0; private int totalPages = 0; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; @@ -57,13 +57,9 @@ class ThumbnailViewChildren extends Children.Keys { this.parent = arg; this.iconSize = iconSize; - // } - // @Override -// protected Node copyNode(Node arg0) { - // return new ThumbnailViewNode(arg0); -// } + @Override protected void addNotify() { super.addNotify(); @@ -85,7 +81,7 @@ class ThumbnailViewChildren extends Children.Keys { //TODO when lazy loading of original nodes is fixed //we should be asking the datamodel for the children instead //and not counting the children nodes (which might not be preloaded at this point) - final List suppContent = new ArrayList(); + final List suppContent = new ArrayList<>(); for (Node child : parent.getChildren().getNodes()) { if (isSupported(child)) { ++totalImages; @@ -122,8 +118,6 @@ class ThumbnailViewChildren extends Children.Keys { pageNums[i] = i + 1; } setKeys(pageNums); - - } @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java index e784d6204b..de26a7977f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java @@ -19,9 +19,17 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Image; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; import java.lang.ref.SoftReference; +import java.util.concurrent.ExecutionException; +import javax.swing.SwingWorker; +import javax.swing.Timer; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; +import org.openide.util.Exceptions; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.datamodel.Content; @@ -31,9 +39,13 @@ import org.sleuthkit.datamodel.Content; */ class ThumbnailViewNode extends FilterNode { + static private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); + private SoftReference iconCache = null; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; - //private final BufferedImage defaultIconBI; + + private SwingWorker swingWorker; + private Timer timer; /** * the constructor @@ -55,29 +67,60 @@ class ThumbnailViewNode extends FilterNode { @Override public Image getIcon(int type) { Image icon = null; - + if (iconCache != null) { icon = iconCache.get(); } - if (icon == null) { - Content content = this.getLookup().lookup(Content.class); - - if (content != null) { - icon = ImageUtils.getThumbnail(content, iconSize); - } else { - icon = ImageUtils.getDefaultThumbnail(); + if (icon != null) { + return icon; + } else { + final Content content = this.getLookup().lookup(Content.class); + if (content == null) { + return ImageUtils.getDefaultThumbnail(); } + if (swingWorker == null || swingWorker.isDone()) { + swingWorker = new SwingWorker() { + final private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("generating thumbnail for video file " + content.getName()); - iconCache = new SoftReference<>(icon); + @Override + protected Image doInBackground() throws Exception { + progressHandle.start(); + return ImageUtils.getThumbnail(content, iconSize); + } + + @Override + protected void done() { + super.done(); + try { + iconCache = new SoftReference<>(super.get()); + progressHandle.finish(); + fireIconChange(); + if (timer != null) { + timer.stop(); + timer = null; + } + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + swingWorker = null; + } + }; + swingWorker.execute(); + } + if (timer == null) { + timer = new Timer(100, (ActionEvent e) -> { + fireIconChange(); + }); + timer.start(); + } + return waitingIcon; } - - return icon; } - + public void setIconSize(int iconSize) { this.iconSize = iconSize; iconCache = null; + swingWorker = null; } - } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 92ffce4e84..77e8dfb42d 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -45,6 +46,7 @@ import javax.annotation.Nullable; import javax.imageio.ImageIO; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.opencv.core.Core; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; @@ -55,6 +57,7 @@ import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskCoreException; /** + * * Utilities for working with Images and creating thumbnails. Reuses thumbnails * by storing them in the case's cache directory. */ @@ -69,44 +72,111 @@ public class ImageUtils { public static final int ICON_SIZE_MEDIUM = 100; public static final int ICON_SIZE_LARGE = 200; - private static final Image DEFAULT_THUMBNAIL; + private static final Logger logger = LOGGER; + private static final BufferedImage DEFAULT_THUMBNAIL; + private static final TreeSet SUPPORTED_MIME_TYPES = new TreeSet<>(); + private static final List SUPPORTED_EXTENSIONS = new ArrayList<>(); + private static final List SUPPORTED_IMAGE_EXTENSIONS; + private static final List SUPPORTED_VIDEO_EXTENSIONS + = Arrays.asList("mov", "m4v", "flv", "mp4", "3gp", "avi", "mpg", + "mpeg", "asf", "divx", "rm", "moov", "wmv", "vob", "dat", + "m1v", "m2v", "m4v", "mkv", "mpe", "yop", "vqa", "xmv", + "mve", "wtv", "webm", "vivo", "vc1", "seq", "thp", "san", + "mjpg", "smk", "vmd", "sol", "cpk", "sdp", "sbg", "rtsp", + "rpl", "rl2", "r3d", "mlp", "mjpeg", "hevc", "h265", "265", + "h264", "h263", "h261", "drc", "avs", "pva", "pmp", "ogg", + "nut", "nuv", "nsv", "mxf", "mtv", "mvi", "mxg", "lxf", + "lvf", "ivf", "mve", "cin", "hnm", "gxf", "fli", "flc", + "flx", "ffm", "wve", "uv2", "dxa", "dv", "cdxl", "cdg", + "bfi", "jv", "bik", "vid", "vb", "son", "avs", "paf", "mm", + "flm", "tmv", "4xm"); //NON-NLS + private static final TreeSet SUPPORTED_IMAGE_MIME_TYPES; + private static final List SUPPORTED_VIDEO_MIME_TYPES + = Arrays.asList("application/x-shockwave-flash", "video/x-m4v", "video/quicktime", "video/avi", "video/msvideo", "video/x-msvideo", + "video/mp4", "video/x-ms-wmv", "video/mpeg", "video/asf"); //NON-NLS + private static final boolean openCVLoaded; - /** initialized lazily */ + static { + ImageIO.scanForPlugins(); + BufferedImage tempImage; + try { + tempImage = ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/file-icon.png"));//NON-NLS + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Failed to load default icon.", ex); + tempImage = null; + } + DEFAULT_THUMBNAIL = tempImage; + + //load opencv libraries + boolean openCVLoadedTemp; + try { + System.loadLibrary(Core.NATIVE_LIBRARY_NAME); + if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { + System.loadLibrary("opencv_ffmpeg248_64"); + } else { + System.loadLibrary("opencv_ffmpeg248"); + } + + openCVLoadedTemp = true; + } catch (UnsatisfiedLinkError e) { + openCVLoadedTemp = false; + LOGGER.log(Level.SEVERE, "OpenCV Native code library failed to load", e); + //TODO: show warning bubble + + } + + openCVLoaded = openCVLoadedTemp; + SUPPORTED_IMAGE_EXTENSIONS = Arrays.asList(ImageIO.getReaderFileSuffixes()); + + SUPPORTED_EXTENSIONS.addAll(SUPPORTED_IMAGE_EXTENSIONS); + SUPPORTED_EXTENSIONS.addAll(SUPPORTED_VIDEO_EXTENSIONS); + + SUPPORTED_IMAGE_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes())); + /* special cases and variants that we support, but don't get registered + * with ImageIO automatically */ + SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList( + "image/x-rgb", + "image/x-ms-bmp", + "application/x-123")); + SUPPORTED_MIME_TYPES.addAll(SUPPORTED_IMAGE_MIME_TYPES); + SUPPORTED_MIME_TYPES.addAll(SUPPORTED_VIDEO_MIME_TYPES); + + //this is rarely usefull + SUPPORTED_MIME_TYPES.removeIf("application/octet-stream"::equals); + } + + /** + * Get the default Icon, which is the icon for a file. + * + * @return + * + * + * + * /** initialized lazily */ private static FileTypeDetector fileTypeDetector; - private static final List SUPPORTED_EXTENSIONS; - private static final TreeSet SUPPORTED_MIME_TYPES; - /** thread that saves generated thumbnails to disk in the background */ private static final Executor imageSaver = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() .namingPattern("icon saver-%d").build()); - static { - ImageIO.scanForPlugins(); - BufferedImage defaultIcon = null; - try { - defaultIcon = ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/file-icon.png"));//NON-NLS - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Failed to read default thumbnail."); - } - DEFAULT_THUMBNAIL = defaultIcon; - - SUPPORTED_EXTENSIONS = Arrays.asList(ImageIO.getReaderFileSuffixes()); - SUPPORTED_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes())); - - /* special cases and variants that we support, but don't get registered - * with ImageIO automatically */ - SUPPORTED_MIME_TYPES.addAll(Arrays.asList( - "image/x-rgb", - "image/x-ms-bmp", - "application/x-123")); - - //this is rarely usefull - SUPPORTED_MIME_TYPES.removeIf("application/octet-stream"::equals); + private ImageUtils() { } - private ImageUtils() { + public static List getSupportedImageExtensions() { + return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS); + } + + public static List getSupportedVideoExtensions() { + return SUPPORTED_VIDEO_EXTENSIONS; + } + + public static SortedSet getSupportedImageMimeTypes() { + return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES); + } + + public static List getSupportedVideoMimeTypes() { + return SUPPORTED_VIDEO_MIME_TYPES; } public static List getSupportedExtensions() { @@ -142,17 +212,22 @@ public class ImageUtils { } /** - * Can a thumbnail be generated for the file? + * Can a thumbnail be generated for the content? * - * @param file + * @param content * * @return * */ - public static boolean thumbnailSupported(AbstractFile file) { - if (file.getSize() == 0) { + public static boolean thumbnailSupported(Content content) { + + if (content.getSize() == 0) { return false; } + if (!(content instanceof AbstractFile)) { + return false; + } + AbstractFile file = (AbstractFile) content; try { String mimeType = getFileTypeDetector().getFileType(file); @@ -162,13 +237,12 @@ public class ImageUtils { } } catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) { LOGGER.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex); - if (!SUPPORTED_MIME_TYPES.isEmpty()) { - AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(SUPPORTED_MIME_TYPES); - if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) { - return true; - } else if (mimeMatch == AbstractFile.MimeMatchEnum.FALSE) { - return false; - } + + AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(SUPPORTED_MIME_TYPES); + if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) { + return true; + } else if (mimeMatch == AbstractFile.MimeMatchEnum.FALSE) { + return false; } } @@ -182,22 +256,6 @@ public class ImageUtils { return isJpegFileHeader(file) || isPngFileHeader(file); } - /** - * Can a thumbnail be generated for the content? - * - * @param content - * - * @return - * - * @deprecated use {@link #thumbnailSupported(org.sleuthkit.datamodel.AbstractFile) instead. - */ - @Deprecated - public static boolean thumbnailSupported(Content content) { - return (content instanceof AbstractFile) - ? thumbnailSupported((AbstractFile) content) - : false; - } - /** * returns a lazily instatiated FileTypeDetector * @@ -220,11 +278,13 @@ public class ImageUtils { * @param content * @param iconSize * + * * @return a thumbnail for the given image or a default one if there was a * problem making a thumbnail. * * @deprecated use {@link #getThumbnail(org.sleuthkit.datamodel.Content, int) * } instead. + * */ @Nonnull @Deprecated @@ -253,8 +313,8 @@ public class ImageUtils { } else { return thumbnail; } - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Error while reading image.", ex); //NON-NLS + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error while reading image: " + content.getName(), ex); //NON-NLS return generateAndSaveThumbnail(content, iconSize, cacheFile); } } else { @@ -274,6 +334,7 @@ public class ImageUtils { * * @deprecated use {@link #getCachedThumbnailFile(org.sleuthkit.datamodel.Content, int) * } instead. + * */ @Nullable @Deprecated @@ -283,6 +344,7 @@ public class ImageUtils { } /** + * * Get a thumbnail of a specified size. Generates the image if it is * not already cached. * @@ -306,15 +368,30 @@ public class ImageUtils { * * @return * - * @deprecated this should never have been public. + * + * @deprecated use {@link #getCachedThumbnailLocation(long) } instead */ @Deprecated + public static File getFile(long id) { return getCachedThumbnailLocation(id); } - private static File getCachedThumbnailLocation(long id) { - return Paths.get(Case.getCurrentCase().getCacheDirectory(), "thumbnails", id + ".png").toFile(); + /** + * Get a file object for where the cached icon should exist. The returned + * file may not exist. + * + * @param fileID + * + * @return + * + */ + private static File getCachedThumbnailLocation(long fileID) { + return Paths.get(Case.getCurrentCase().getCacheDirectory(), "thumbnails", fileID + ".png").toFile(); + } + + public static boolean hasImageFileHeader(AbstractFile file) { + return isJpegFileHeader(file) || isPngFileHeader(file); } /** @@ -379,36 +456,54 @@ public class ImageUtils { } /** - * Generate a thumbnail and save it to specified location. + * Generate an icon and save it to specified location. * * @param content File to generate icon for - * @param size the size of thumbnail to generate in pixels + * @param iconSize * @param cacheFile Location to save thumbnail to * - * @return Generated icon or a default icon if a thumbnail could not be - * made. + * @return Generated icon or null on error */ - private static Image generateAndSaveThumbnail(Content content, int size, File cacheFile) { - BufferedImage thumbnail = generateThumbnail(content, size); - if (Objects.nonNull(thumbnail)) { - imageSaver.execute(() -> { - try { - Files.createParentDirs(cacheFile); - if (cacheFile.exists()) { - cacheFile.delete(); - } - ImageIO.write(thumbnail, FORMAT, cacheFile); - } catch (IllegalArgumentException | IOException ex1) { - LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex1); //NON-NLS + private static Image generateAndSaveThumbnail(Content content, int iconSize, File cacheFile) { + AbstractFile f = (AbstractFile) content; + final String extension = f.getNameExtension(); + BufferedImage thumbnail = null; + try { + if (SUPPORTED_VIDEO_EXTENSIONS.contains(extension)) { + if (openCVLoaded) { + thumbnail = VideoUtils.generateVideoThumbnail((AbstractFile) content, iconSize); + } else { + return DEFAULT_THUMBNAIL; } - }); - return thumbnail; - } else { - return getDefaultThumbnail(); + } else { + thumbnail = generateImageThumbnail(content, iconSize); + } + + if (thumbnail == null) { + return DEFAULT_THUMBNAIL; + + } else { + BufferedImage toSave = thumbnail; + imageSaver.execute(() -> { + try { + Files.createParentDirs(cacheFile); + if (cacheFile.exists()) { + cacheFile.delete(); + } + ImageIO.write(toSave, FORMAT, cacheFile); + } catch (IllegalArgumentException | IOException ex1) { + LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex1); //NON-NLS + } + }); + } + } catch (NullPointerException ex) { + logger.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex); //NON-NLS } + return thumbnail; } /** + * * Generate and return a scaled image * * @param content @@ -418,7 +513,7 @@ public class ImageUtils { * there was a problem. */ @Nullable - private static BufferedImage generateThumbnail(Content content, int iconSize) { + private static BufferedImage generateImageThumbnail(Content content, int iconSize) { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(content));) { BufferedImage bi = ImageIO.read(inputStream); @@ -436,10 +531,13 @@ public class ImageUtils { } } catch (OutOfMemoryError e) { LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS + return null; } catch (Exception e) { - LOGGER.log(Level.WARNING, "Could not scale image: " + content.getName(), e); //NON-NLS + LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS return null; + } } + } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java new file mode 100644 index 0000000000..2f5725aac1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java @@ -0,0 +1,127 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.coreutils; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.logging.Level; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.opencv.core.Mat; +import org.opencv.highgui.VideoCapture; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corelibs.ScalrWrapper; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * + */ +public class VideoUtils { + + private static final int THUMB_COLUMNS = 3; + private static final int THUMB_ROWS = 3; + private static final int CV_CAP_PROP_POS_MSEC = 0; + private static final int CV_CAP_PROP_FRAME_COUNT = 7; + private static final int CV_CAP_PROP_FPS = 5; + + static final Logger LOGGER = Logger.getLogger(VideoUtils.class.getName()); + + private VideoUtils() { + } + + public static File getTempVideoFile(AbstractFile file) { + return Paths.get(Case.getCurrentCase().getTempDirectory(), "videos", file.getId() + "." + file.getNameExtension()).toFile(); + } + + static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { + java.io.File tempFile = getTempVideoFile(file); + + try { + if (tempFile.exists() == false || tempFile.length() < file.getSize()) { + com.google.common.io.Files.createParentDirs(tempFile); + ProgressHandle progress = ProgressHandleFactory.createHandle("extracting temporary file " + file.getName()); + progress.start(100); + try { + ContentUtils.writeToFile(file, tempFile, progress, null, true); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Error buffering file", ex); //NON-NLS + } + progress.finish(); + } + } catch (IOException ex) { + return null; + } + + VideoCapture videoFile = new VideoCapture(); // will contain the video + + if (!videoFile.open(tempFile.toString())) { + return null; + } + double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second + double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames + if (fps <= 0 || totalFrames <= 0) { + return null; + } + double milliseconds = 1000 * (totalFrames / fps); //total milliseconds + + double timestamp = Math.min(milliseconds, 500); //default time to check for is 500ms, unless the files is extremely small + + int framkeskip = Double.valueOf(Math.floor((milliseconds - timestamp) / (THUMB_COLUMNS * THUMB_ROWS))).intValue(); + + Mat imageMatrix = new Mat(); + BufferedImage bufferedImage = null; + + for (int x = 0; x < THUMB_COLUMNS; x++) { + for (int y = 0; y < THUMB_ROWS; y++) { + if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) { + break; + } + //read the frame into the image/matrix + if (!videoFile.read(imageMatrix)) { + break; //if the image for some reason is bad, return default icon + } + + if (bufferedImage == null) { + bufferedImage = new BufferedImage(imageMatrix.cols() * THUMB_COLUMNS, imageMatrix.rows() * THUMB_ROWS, BufferedImage.TYPE_3BYTE_BGR); + } + + byte[] data = new byte[imageMatrix.rows() * imageMatrix.cols() * (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]; + data[k] = data[k + 2]; + data[k + 2] = temp; + } + } + + bufferedImage.getRaster().setDataElements(imageMatrix.cols() * x, imageMatrix.rows() * y, imageMatrix.cols(), imageMatrix.rows(), data); + } + } + + videoFile.release(); // close the file + + return ScalrWrapper.resizeFast(bufferedImage, iconSize); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java index 3223e29382..3860a9b0dd 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java @@ -23,14 +23,15 @@ import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.TimeZone; +import java.util.concurrent.Future; import java.util.logging.Level; import java.util.prefs.PreferenceChangeEvent; import java.util.prefs.PreferenceChangeListener; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.SwingWorker; import org.netbeans.api.progress.ProgressHandle; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; @@ -176,8 +177,8 @@ public final class ContentUtils { * @return number of bytes extracted * @throws IOException if file could not be written */ - public static long writeToFile(Content content, java.io.File outputFile, - ProgressHandle progress, SwingWorker worker, boolean source) throws IOException { + public static long writeToFile(Content content, java.io.File outputFile, + ProgressHandle progress, Future worker, boolean source) throws IOException { InputStream in = new ReadContentInputStream(content); diff --git a/Core/src/org/sleuthkit/autopsy/images/working_spinner.gif b/Core/src/org/sleuthkit/autopsy/images/working_spinner.gif new file mode 100644 index 0000000000..8d23781860 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/working_spinner.gif differ diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java index 0112d12bbb..9f1eb992f1 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java @@ -382,7 +382,7 @@ public class IngestJobSettings { if (isPythonModuleSettingsFile(moduleSettingsFilePath)) { // compiled python modules have variable instance number as a part of their file name. // This block of code gets rid of that variable instance number and helps maitains constant module name over multiple runs. - moduleSettingsFilePath.replaceAll("[$][\\d]+.settings$", "\\$.settings"); + moduleSettingsFilePath = moduleSettingsFilePath.replaceAll("[$][\\d]+.settings$", "\\$.settings"); } try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(moduleSettingsFilePath))) { out.writeObject(settings); diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties index 41acbcf61d..2bb4e97908 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties @@ -13,4 +13,8 @@ cannotCreateOutputDir.message=Unable to create output directory: {0} PhotoRecIngestModule.processTerminated=PhotoRec Carver ingest module was terminated due to exceeding max allowable run time when scanning PhotoRecIngestModule.moduleError=PhotoRec Carver Module Error PhotoRecIngestModule.UnableToCarve=Unable to carve file: {0} -PhotoRecIngestModule.NotEnoughDiskSpace=Not enough disk space to save unallocated file. Carving will be skipped. \ No newline at end of file +PhotoRecIngestModule.NotEnoughDiskSpace=Not enough disk space to save unallocated file. Carving will be skipped. +PhotoRecIngestModule.complete.numberOfCarved=Number of Files Carved\: +PhotoRecIngestModule.complete.totalWritetime=Total Time To Write To Disk +PhotoRecIngestModule.complete.totalParsetime=Total Parsing Time +PhotoRecIngestModule.complete.photoRecResults=PhotoRec Results \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index f168d20e84..e0277e524a 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -32,7 +32,9 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle; @@ -46,6 +48,7 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator; import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestModule; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestServices; @@ -71,12 +74,30 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { private static final String LOG_FILE = "run_log.txt"; //NON-NLS private static final String TEMP_DIR_NAME = "temp"; // NON-NLS private static final Logger logger = Logger.getLogger(PhotoRecCarverFileIngestModule.class.getName()); + private static final HashMap totalsForIngestJobs = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private static final Map pathsByJob = new ConcurrentHashMap<>(); private IngestJobContext context; private Path rootOutputDirPath; private File executableFile; private IngestServices services; + private long jobId; + + private static class IngestJobTotals { + + private AtomicLong totalItemsRecovered = new AtomicLong(0); + private AtomicLong totalWritetime = new AtomicLong(0); + private AtomicLong totalParsetime = new AtomicLong(0); + } + + private static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId) { + IngestJobTotals totals = totalsForIngestJobs.get(ingestJobId); + if (totals == null) { + totals = new PhotoRecCarverFileIngestModule.IngestJobTotals(); + totalsForIngestJobs.put(ingestJobId, totals); + } + return totals; + } /** * @inheritDoc @@ -85,6 +106,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { this.context = context; this.services = IngestServices.getInstance(); + this.jobId = this.context.getJobId(); // If the global unallocated space processing setting and the module // process unallocated space only setting are not in sych, throw an @@ -99,7 +121,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE); executableFile = locateExecutable(execName.toString()); - if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.context.getJobId()) == 1) { + if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.jobId) == 1) { try { // The first instance creates an output subdirectory with a date and time stamp DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS"); // NON-NLS @@ -113,9 +135,8 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { Files.createDirectory(tempDirPath); // Save the directories for the current job. - PhotoRecCarverFileIngestModule.pathsByJob.put(this.context.getJobId(), new WorkingPaths(outputDirPath, tempDirPath)); - } - catch (SecurityException | IOException | UnsupportedOperationException ex) { + PhotoRecCarverFileIngestModule.pathsByJob.put(this.jobId, new WorkingPaths(outputDirPath, tempDirPath)); + } catch (SecurityException | IOException | UnsupportedOperationException ex) { throw new IngestModule.IngestModuleException(NbBundle.getMessage(this.getClass(), "cannotCreateOutputDir.message", ex.getLocalizedMessage())); } } @@ -130,6 +151,9 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { if (file.getType() != TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { return IngestModule.ProcessResult.OK; } + + // Safely get a reference to the totalsForIngestJobs object + IngestJobTotals totals = getTotalsForIngestJobs(jobId); Path tempFilePath = null; try { @@ -160,7 +184,8 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { } // Write the file to disk. - WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.context.getJobId()); + long writestart = System.currentTimeMillis(); + WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.jobId); tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName()); ContentUtils.writeToFile(file, tempFilePath.toFile()); @@ -184,7 +209,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { processAndSettings.redirectOutput(Redirect.appendTo(log)); int exitValue = ExecUtil.execute(processAndSettings, new FileIngestModuleProcessTerminator(this.context)); - + if (this.context.fileIngestIsCancelled() == true) { // if it was cancelled by the user, result is OK cleanup(outputDirPath, tempFilePath); @@ -211,21 +236,24 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { } } } + long writedelta = (System.currentTimeMillis() - writestart); + totals.totalWritetime.addAndGet(writedelta); // Now that we've cleaned up the folders and data files, parse the xml output file to add carved items into the database + long calcstart = System.currentTimeMillis(); PhotoRecCarverOutputParser parser = new PhotoRecCarverOutputParser(outputDirPath); - List theList = parser.parse(newAuditFile, id, file); - if (theList != null) { // if there were any results from carving, add the unallocated carving event to the reports list. - context.addFilesToJob(new ArrayList<>(theList)); - services.fireModuleContentEvent(new ModuleContentEvent(theList.get(0))); // fire an event to update the tree + List carvedItems = parser.parse(newAuditFile, id, file); + long calcdelta = (System.currentTimeMillis() - calcstart); + totals.totalParsetime.addAndGet(calcdelta); + if (carvedItems != null) { // if there were any results from carving, add the unallocated carving event to the reports list. + totals.totalItemsRecovered.addAndGet(carvedItems.size()); + context.addFilesToJob(new ArrayList<>(carvedItems)); + services.fireModuleContentEvent(new ModuleContentEvent(carvedItems.get(0))); // fire an event to update the tree } - } - catch (IOException ex) { + } catch (IOException ex) { logger.log(Level.SEVERE, "Error processing " + file.getName() + " with PhotoRec carver", ex); // NON-NLS return IngestModule.ProcessResult.ERROR; - } - - finally { + } finally { if (null != tempFilePath && Files.exists(tempFilePath)) { // Get rid of the unallocated space file. tempFilePath.toFile().delete(); @@ -234,7 +262,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { return IngestModule.ProcessResult.OK; } - + private void cleanup(Path outputDirPath, Path tempFilePath) { // cleanup the output path FileUtil.deleteDir(new File(outputDirPath.toString())); @@ -243,19 +271,48 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { } } + private synchronized void postSummary() { + IngestJobTotals jobTotals = totalsForIngestJobs.remove(jobId); + + StringBuilder detailsSb = new StringBuilder(); + //details + detailsSb.append(""); //NON-NLS + + detailsSb.append(""); //NON-NLS + detailsSb.append(""); //NON-NLS + + detailsSb.append("\n"); //NON-NLS + detailsSb.append("\n"); //NON-NLS + detailsSb.append("
") //NON-NLS + .append(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.complete.numberOfCarved")) + .append("").append(jobTotals.totalItemsRecovered.get()).append("
") //NON-NLS + .append(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.complete.totalWritetime")) + .append("").append(jobTotals.totalWritetime.get()).append("
") //NON-NLS + .append(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.complete.totalParsetime")) + .append("").append(jobTotals.totalParsetime.get()).append("
"); //NON-NLS + + services.postMessage(IngestMessage.createMessage( + IngestMessage.MessageType.INFO, + PhotoRecCarverIngestModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), + "PhotoRecIngestModule.complete.photoRecResults"), + detailsSb.toString())); + + } /** * @inheritDoc */ @Override public void shutDown() { - if (this.context != null && refCounter.decrementAndGet(this.context.getJobId()) == 0) { + if (this.context != null && refCounter.decrementAndGet(this.jobId) == 0) { try { // The last instance of this module for an ingest job cleans out // the working paths map entry for the job and deletes the temp dir. - WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.context.getJobId()); + WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.jobId); FileUtil.deleteDir(new File(paths.getTempDirPath().toString())); - } + postSummary(); + } catch (SecurityException ex) { logger.log(Level.SEVERE, "Error shutting down PhotoRec carver module", ex); // NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java index d02da89609..28230d4dc2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java @@ -251,8 +251,8 @@ public interface TimeLineChart extends TimeLineView { final X end = getSpanEnd(); Tooltip.uninstall(this, tooltip); tooltip = new Tooltip( - NbBundle.getMessage(this.getClass(), "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start), - formatSpan(end))); + NbBundle.getMessage(TimeLineChart.class, "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start), + formatSpan(end))); Tooltip.install(this, tooltip); } diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml index ab6cd75280..337b411e37 100644 --- a/ImageGallery/nbproject/project.xml +++ b/ImageGallery/nbproject/project.xml @@ -4,7 +4,7 @@ org.sleuthkit.autopsy.imagegallery - + org.netbeans.api.progress @@ -103,7 +103,7 @@ 10 - 10.0.11 + 10.3 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java index 9beb1f3798..e9912e6b8e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java @@ -26,6 +26,7 @@ import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; import java.util.Set; +import java.util.TreeSet; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -80,6 +81,10 @@ public enum FileTypeUtils { */ private static FileTypeDetector FILE_TYPE_DETECTOR; + private static final String IMAGE_GIF_MIME = "image/gif"; + + private static final TreeSet GIF_MIME_SET = new TreeSet<>(Arrays.asList(IMAGE_GIF_MIME)); + /** * static initalizer block to initialize sets of extensions and mimetypes * to be supported @@ -164,7 +169,7 @@ public enum FileTypeUtils { * * @return true if this file is supported or false if not */ - public static Boolean isDrawable(AbstractFile file) { + public static boolean isDrawable(AbstractFile file) { return hasDrawableMimeType(file).orElseGet(() -> { final boolean contains = FileTypeUtils.supportedExtensions.contains(file.getNameExtension()); final boolean jpegFileHeader = ImageUtils.isJpegFileHeader(file); @@ -175,6 +180,30 @@ public enum FileTypeUtils { }); } + public static boolean isGIF(AbstractFile file) { + try { + final FileTypeDetector fileTypeDetector = getFileTypeDetector(); + if (nonNull(fileTypeDetector)) { + String fileType = fileTypeDetector.getFileType(file); + return IMAGE_GIF_MIME.equalsIgnoreCase(fileType); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Failed to get mime type with FileTypeDetector.", ex); + } + LOGGER.log(Level.WARNING, "Falling back on direct mime type check."); + switch (file.isMimeType(GIF_MIME_SET)) { + + case TRUE: + return true; + case UNDEFINED: + LOGGER.log(Level.WARNING, "Falling back on extension check."); + return "gif".equals(file.getNameExtension()); + case FALSE: + default: + return false; + } + } + /** * does the given file have drawable/supported mime type * diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index ade11f3165..b4b01984db 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -462,7 +462,7 @@ public final class ImageGalleryController { //this file should be included and we don't already know about it from hash sets (NSRL) queueDBWorkerTask(new UpdateFileTask(file, db)); } else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) { - //doing this check results in fewer tasks queued up, and faster completion of db update + //doing this check results in fewer tasks queued up, and faster completion of db update //this file would have gotten scooped up in initial grab, but actually we don't need it queueDBWorkerTask(new RemoveFileTask(file, db)); } @@ -751,9 +751,9 @@ public final class ImageGalleryController { "' or name LIKE '%.") + "')"; static private final String MIMETYPE_CLAUSE - = "blackboard_attributes.value_text LIKE " + = "blackboard_attributes.value_text LIKE '" + StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(), - " OR blackboard_attributes.value_text LIKE "); + "' OR blackboard_attributes.value_text LIKE '") + "' "; static private final String DRAWABLE_QUERY = FILE_EXTESNION_CLAUSE + " OR tsk_files.obj_id IN (" + "SELECT tsk_files.obj_id from tsk_files , blackboard_artifacts, blackboard_attributes" diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index fb8c0a83f9..29ae30b181 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.util.concurrent.UncheckedExecutionException; import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; @@ -42,6 +43,7 @@ import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; +import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskCoreException; /** Singleton to manage creation and access of icons. Keeps a cache in memory of @@ -54,7 +56,7 @@ public enum ThumbnailCache { instance; - private static final int MAX_ICON_SIZE = 300; + private static final int MAX_THUMBNAIL_SIZE = 300; private static final Logger LOGGER = Logger.getLogger(ThumbnailCache.class.getName()); @@ -118,38 +120,44 @@ public enum ThumbnailCache { */ private Optional load(DrawableFile file) { - BufferedImage thumbnail; - try { - thumbnail = getCacheFile(file).map(new Function() { - @Override - public BufferedImage apply(File cachFile) { - if (cachFile.exists()) { - // If a thumbnail file is already saved locally, load it - try { - BufferedImage read = ImageIO.read(cachFile); - if (read.getWidth() == MAX_ICON_SIZE) { - return read; - } - } catch (MalformedURLException ex) { - LOGGER.log(Level.WARNING, "Unable to parse cache file path.."); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - } - return null; - } - }).orElseGet(() -> { - return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_ICON_SIZE); - }); - - } catch (IllegalStateException e) { - LOGGER.log(Level.WARNING, "can't load icon when no case is open"); - return Optional.empty(); + if (FileTypeUtils.isGIF(file)) { + //directly read gif to preserve potential animation, + //NOTE: not saved to disk! + return Optional.of(new Image(new BufferedInputStream(new ReadContentInputStream(file.getAbstractFile())), MAX_THUMBNAIL_SIZE, MAX_THUMBNAIL_SIZE, true, true)); } + + BufferedImage thumbnail = getCacheFile(file).map(new Function() { + @Override + public BufferedImage apply(File cachFile) { + if (cachFile.exists()) { + // If a thumbnail file is already saved locally, load it + try { + BufferedImage read = ImageIO.read(cachFile); + + if (read.getWidth() < MAX_THUMBNAIL_SIZE) { + return read; + + } + } catch (MalformedURLException ex) { + LOGGER.log(Level.WARNING, "Unable to parse cache file path.."); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + return null; + } + }).orElseGet(() -> { + return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE); + }); + +// } catch (IllegalStateException e) { +// LOGGER.log(Level.WARNING, "can't load icon when no case is open"); +// return Optional.empty(); +// } WritableImage jfxthumbnail; if (thumbnail == ImageUtils.getDefaultThumbnail()) { - jfxthumbnail = null // if we go the default icon, ignore it - ; + // if we go the default icon, ignore it + jfxthumbnail = null; } else { jfxthumbnail = SwingFXUtils.toFXImage(thumbnail, null); } @@ -167,7 +175,8 @@ public enum ThumbnailCache { */ private static Optional getCacheFile(DrawableFile file) { try { - return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_ICON_SIZE)); + return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_THUMBNAIL_SIZE)); + } catch (IllegalStateException e) { LOGGER.log(Level.WARNING, "Failed to create cache file.{0}", e.getLocalizedMessage()); return Optional.empty(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java index 4399a05706..6201663428 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +26,6 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javafx.application.Platform; import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; import javafx.scene.control.Menu; import javax.swing.SwingWorker; import org.openide.util.Utilities; @@ -91,19 +90,16 @@ public class AddDrawableTagAction extends AddTagAction { .findAny(); if (duplicateTagName.isPresent()) { - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.WARNING, "Unable to tag " + file.getName() + ". It has already been tagged as \"" + tagName.getDisplayName() + ". Cannot reapply the same tag.", ButtonType.OK); - alert.setHeaderText("Tag Error"); - alert.show(); - }); + LOGGER.log(Level.INFO, "{0} already tagged as {1}. Skipping.", new Object[]{file.getName(), tagName.getDisplayName()}); } else { + LOGGER.log(Level.INFO, "Tagging {0} as {1}", new Object[]{file.getName(), tagName.getDisplayName()}); controller.getTagsManager().addContentTag(file, tagName, comment); } } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.SEVERE, "Error tagging result", tskCoreException); + LOGGER.log(Level.SEVERE, "Error tagging file", tskCoreException); Platform.runLater(() -> { - new Alert(Alert.AlertType.ERROR, "Unable to file " + fileID + ".").show(); + new Alert(Alert.AlertType.ERROR, "Unable to tag file " + fileID + ".").show(); }); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 9baa52acc5..da58bd07bf 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -18,10 +18,12 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel; +import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; @@ -37,14 +39,10 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; +import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE; -import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE; -import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER; -import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG; -import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.ReadContentInputStream; @@ -90,6 +88,8 @@ public abstract class DrawableFile extends AbstractFile } } + SoftReference imageRef; + private String drawablePath; protected T file; @@ -286,7 +286,12 @@ public abstract class DrawableFile extends AbstractFile } } - public abstract Image getThumbnail(); + + public Image getThumbnail() { + return ThumbnailCache.getDefault().get(this); + } + + public abstract Image getFullSizeImage(); public void setAnalyzed(Boolean analyzed) { this.analyzed.set(analyzed); @@ -318,5 +323,8 @@ public abstract class DrawableFile extends AbstractFile } } - public abstract boolean isDisplayable(); + public boolean isDisplayableAsImage() { + Image thumbnail = getThumbnail(); + return Objects.nonNull(thumbnail) && thumbnail.errorProperty().get() == false; + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index a5e27fe30c..2973a435ed 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -22,13 +22,12 @@ import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.IOException; import java.lang.ref.SoftReference; -import java.util.Objects; import java.util.logging.Level; import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; import javax.imageio.ImageIO; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; +import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.ReadContentInputStream; @@ -45,22 +44,20 @@ public class ImageFile extends DrawableFile { ImageIO.scanForPlugins(); } - private SoftReference imageRef; - ImageFile(T f, Boolean analyzed) { super(f, analyzed); } - @Override - public Image getThumbnail() { - return ThumbnailCache.getDefault().get(this); - } + @Override public Image getFullSizeImage() { - Image image = null; - if (imageRef != null) { - image = imageRef.get(); + Image image = (imageRef != null) ? imageRef.get() : null; + if (image == null || image.isError()) { + if (FileTypeUtils.isGIF(file)) { + //directly read gif to preserve potential animation, + image = new Image(new BufferedInputStream(new ReadContentInputStream(file))); + } } if (image == null || image.isError()) { try (BufferedInputStream readContentInputStream = new BufferedInputStream(new ReadContentInputStream(this.getAbstractFile()))) { @@ -75,12 +72,6 @@ public class ImageFile extends DrawableFile { return image; } - @Override - public boolean isDisplayable() { - Image thumbnail = getThumbnail(); - return Objects.nonNull(thumbnail) && thumbnail.errorProperty().get() == false; - } - @Override Double getWidth() { final Image fullSizeImage = getFullSizeImage(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index fbe7ff60b5..19fa08a8c7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -19,17 +19,22 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import com.google.common.io.Files; +import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; import java.nio.file.Paths; import java.util.Objects; import java.util.logging.Level; +import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; import javafx.scene.media.Media; import javafx.scene.media.MediaException; -import org.sleuthkit.autopsy.casemodule.Case; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.VideoUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; @@ -46,25 +51,36 @@ public class VideoFile extends DrawableFile { } @Override - public Image getThumbnail() { - //TODO: implement video thumbnailing here? - return getGenericVideoThumbnail(); + public Image getFullSizeImage() { + Image image = (null == imageRef) ? null : imageRef.get(); + + if (image == null) { + final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024); + image = (bufferedImage == ImageUtils.getDefaultThumbnail()) ? null : SwingFXUtils.toFXImage(bufferedImage, null); + imageRef = new SoftReference<>(image); + } + + return image; } - SoftReference mediaRef; + private SoftReference mediaRef; public Media getMedia() throws IOException, MediaException { - Media media = null; - if (mediaRef != null) { - media = mediaRef.get(); - } + Media media = (mediaRef != null) ? mediaRef.get() : null; + if (media != null) { return media; } - final File cacheFile = getCacheFile(this.getId()); - if (cacheFile.exists() == false) { + final File cacheFile = VideoUtils.getTempVideoFile(this.getAbstractFile()); + + if (cacheFile.exists() == false || cacheFile.length() < getAbstractFile().getSize()) { + Files.createParentDirs(cacheFile); - ContentUtils.writeToFile(this.getAbstractFile(), cacheFile); + ProgressHandle progressHandle = ProgressHandleFactory.createHandle("writing temporary file to disk"); + progressHandle.start(100); + ContentUtils.writeToFile(this.getAbstractFile(), cacheFile, progressHandle, null, true); + progressHandle.finish(); + } media = new Media(Paths.get(cacheFile.getAbsolutePath()).toUri().toString()); @@ -73,12 +89,7 @@ public class VideoFile extends DrawableFile { } - private File getCacheFile(long id) { - return Paths.get(Case.getCurrentCase().getCacheDirectory(), "videos", "" + id).toFile(); - } - - @Override - public boolean isDisplayable() { + public boolean isDisplayableAsMedia() { try { Media media = getMedia(); return Objects.nonNull(media) && Objects.isNull(media.getError()); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index ea8cb26b00..43cb88d778 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -44,7 +44,6 @@ import org.openide.util.Exceptions; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; -import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; @@ -208,7 +207,6 @@ public class Toolbar extends ToolBar { orderGroup.selectedToggleProperty().addListener(queryInvalidationListener); - ThumbnailCache.getDefault().iconSize.bind(sizeSlider.valueProperty()); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MediaControl.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/VideoPlayer.java similarity index 88% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MediaControl.java rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/VideoPlayer.java index a072f48094..9b60278865 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MediaControl.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/VideoPlayer.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; -import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; import java.util.logging.Level; @@ -27,7 +26,6 @@ import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.Slider; @@ -46,7 +44,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; -public class MediaControl extends BorderPane { +public class VideoPlayer extends BorderPane { private static final Image VOLUME_HIGH = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume.png"); private static final Image VOLUME_LOW = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume-low.png"); @@ -107,21 +105,6 @@ public class MediaControl extends BorderPane { }; private final VideoFile file; - public static Node create(VideoFile file) { - try { - return new MediaControl(new MediaPlayer(file.getMedia()), file); - } catch (IOException ex) { - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "failed to initialize MediaControl for file " + file.getName(), ex); - return new Text(ex.getLocalizedMessage() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action."); - } catch (MediaException ex) { - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, ex.getType() + " Failed to initialize MediaControl for file " + file.getName(), ex); - return new Text(ex.getType() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action."); - } catch (OutOfMemoryError ex) { - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "failed to initialize MediaControl for file " + file.getName(), ex); - return new Text("There was a problem playing video file.\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action."); - } - } - @FXML void initialize() { assert controlButton != null : "fx:id=\"controlButton\" was not injected: check your FXML file 'MediaControl.fxml'."; @@ -250,7 +233,7 @@ public class MediaControl extends BorderPane { } } - private MediaControl(MediaPlayer mp, VideoFile file) { + public VideoPlayer(MediaPlayer mp, VideoFile file) { this.file = file; this.mp = mp; FXMLConstructor.construct(this, "MediaControl.fxml"); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java index 72881906cc..35fad38576 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java @@ -26,12 +26,10 @@ import javafx.scene.CacheHint; import javafx.scene.control.Control; import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javafx.scene.paint.Color; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; -import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; +import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableTileBase.globalSelectionModel; import org.sleuthkit.datamodel.AbstractContent; @@ -50,17 +48,6 @@ public class DrawableTile extends DrawableTileBase { private static final Logger LOGGER = Logger.getLogger(DrawableTile.class.getName()); - /** - * the central ImageView that shows a thumbnail of the represented file - */ - @FXML - private ImageView imageView; - - @Override - protected void disposeContent() { - //no-op - } - @FXML @Override protected void initialize() { @@ -90,12 +77,6 @@ public class DrawableTile extends DrawableTileBase { FXMLConstructor.construct(this, "DrawableTile.fxml"); } - @Override - @ThreadConfined(type = ThreadType.JFX) - protected void clearContent() { - imageView.setImage(null); - } - /** * {@inheritDoc } */ @@ -109,21 +90,13 @@ public class DrawableTile extends DrawableTileBase { } @Override - protected Runnable getContentUpdateRunnable() { - if (getFile().isPresent()) { - Image image = getFile().get().getThumbnail(); - - return () -> { - imageView.setImage(image); - }; - } else { - return () -> { //no-op - }; - } + CachedLoaderTask> getNewImageLoadTask(DrawableFile file) { + return new ThumbnailLoaderTask(file); } @Override protected String getTextForLabel() { return getFile().map(AbstractContent::getName).orElse(""); } + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index 1b7dec6333..1bb1aa2ff2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -54,8 +54,6 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; -import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -71,6 +69,7 @@ import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction; import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; +import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; @@ -106,7 +105,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { @FXML protected ImageView undisplayableImageView; - ** displays the icon representing follow up tag */ + /** displays the icon representing follow up tag */ @FXML private ImageView followUpImageView; @@ -120,8 +119,13 @@ public abstract class DrawableTileBase extends DrawableUIBase { @FXML Label nameLabel; - /** the groupPane this {@link DrawableTileBase} is embedded in */ + @FXML + protected ImageView imageView; + /** + * the groupPane this {@link DrawableTileBase} is embedded in + */ final private GroupPane groupPane; + volatile private boolean registered = false; protected DrawableTileBase(GroupPane groupPane) { @@ -235,13 +239,6 @@ public abstract class DrawableTileBase extends DrawableUIBase { return groupPane; } - @ThreadConfined(type = ThreadType.UI) - protected abstract void clearContent(); - - protected abstract void disposeContent(); - - protected abstract Runnable getContentUpdateRunnable(); - protected abstract String getTextForLabel(); protected void initialize() { @@ -279,6 +276,8 @@ public abstract class DrawableTileBase extends DrawableUIBase { @Override synchronized protected void setFileHelper(final Long newFileID) { setFileIDOpt(Optional.ofNullable(newFileID)); + setFileOpt(Optional.empty()); + disposeContent(); if (getFileID().isPresent() == false || Case.isCaseOpen() == false) { @@ -287,23 +286,18 @@ public abstract class DrawableTileBase extends DrawableUIBase { getController().getTagsManager().unregisterListener(this); registered = false; } - setFileOpt(Optional.empty()); - Platform.runLater(() -> { - clearContent(); - }); + updateContent(); } else { if (registered == false) { getController().getCategoryManager().registerListener(this); getController().getTagsManager().registerListener(this); registered = true; } - setFileOpt(Optional.empty()); - updateSelectionState(); updateCategory(); updateFollowUpIcon(); updateUI(); - Platform.runLater(getContentUpdateRunnable()); + updateContent(); } } @@ -311,7 +305,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { getFile().ifPresent(file -> { final boolean isVideo = file.isVideo(); final boolean hasHashSetHits = hasHashHit(); - final boolean isUndisplayable = file.isDisplayable() == false; + final boolean isUndisplayable = (isVideo ? ((VideoFile) file).isDisplayableAsMedia() : file.isDisplayableAsImage()) == false; final String text = getTextForLabel(); Platform.runLater(() -> { @@ -387,4 +381,40 @@ public abstract class DrawableTileBase extends DrawableUIBase { followUpToggle.setSelected(hasFollowUp); }); } + +// @Override +// Node getContentNode() { +// if (getFile().isPresent() == false) { +// imageCache = null; +// Platform.runLater(() -> { +// imageView.setImage(null); +// }); +// return null; +// } else { +// Image thumbnail = isNull(imageCache) ? null : imageCache.get(); +// +// if (nonNull(thumbnail)) { +// Platform.runLater(() -> { +// imageView.setImage(thumbnail); +// }); +// return imageView; +// } else { +// DrawableFile file = getFile().get(); +// +// if (isNull(imageTask) || imageTask.isDone()) { +// imageTask = new ImageLoadTask(file) { +// +// @Override +// void saveToCache(Image result) { +// synchronized (DrawableTileBase.this) { +// imageCache = new SoftReference<>(result); +// } +// } +// }; +// new Thread(imageTask).start(); +// } +// return getLoadingProgressIndicator(); +// } +// } +// } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 267632ed85..f238e454b1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -18,11 +18,22 @@ */ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; +import java.lang.ref.SoftReference; import java.util.Objects; +import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.BorderPane; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; @@ -33,11 +44,21 @@ import org.sleuthkit.datamodel.TskCoreException; */ abstract public class DrawableUIBase extends AnchorPane implements DrawableView { + private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); + + @FXML + protected BorderPane imageBorder; + @FXML + protected ImageView imageView; + private final ImageGalleryController controller; private Optional> fileOpt = Optional.empty(); private Optional fileIDOpt = Optional.empty(); + private Task imageTask; + private SoftReference imageCache; + private ProgressIndicator progressIndicator; public DrawableUIBase(ImageGalleryController controller) { this.controller = controller; @@ -86,10 +107,133 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView synchronized public void setFile(Long newFileID) { if (getFileID().isPresent()) { if (Objects.equals(newFileID, getFileID().get()) == false) { - setFileHelper(newFileID); + if (Objects.nonNull(newFileID)) { + setFileHelper(newFileID); + } } - } else if (nonNull(newFileID)) { + } else if (Objects.nonNull(newFileID)) { setFileHelper(newFileID); } } + + synchronized protected void updateContent() { + Node content = getContentNode(); + Platform.runLater(() -> { + imageBorder.setCenter(content); + }); + } + + synchronized protected void disposeContent() { + if (imageTask != null) { + imageTask.cancel(true); + } + imageTask = null; + imageCache = null; + } + + ProgressIndicator getLoadingProgressIndicator() { + if (progressIndicator == null) { + progressIndicator = new ProgressIndicator(); + } + return progressIndicator; + } + + Node getContentNode() { + if (getFile().isPresent() == false) { + imageCache = null; + Platform.runLater(() -> { + if (imageView != null) { + imageView.setImage(null); + } + }); + return null; + } else { + Image thumbnail = isNull(imageCache) ? null : imageCache.get(); + + if (nonNull(thumbnail)) { + Platform.runLater(() -> { + if (imageView != null) { + imageView.setImage(thumbnail); + } + }); + return imageView; + } else { + DrawableFile file = getFile().get(); + + if (isNull(imageTask)) { + imageTask = getNewImageLoadTask(file); + new Thread(imageTask).start(); + } else if (imageTask.isDone()) { + return null; + } + return getLoadingProgressIndicator(); + } + } + } + + abstract CachedLoaderTask> getNewImageLoadTask(DrawableFile file); + + abstract class CachedLoaderTask> extends Task { + + protected final Y file; + + public CachedLoaderTask(Y file) { + this.file = file; + } + + @Override + protected X call() throws Exception { + return (isCancelled() == false) ? load() : null; + } + + abstract X load(); + + @Override + protected void succeeded() { + super.succeeded(); + if (isCancelled() == false) { + try { + saveToCache(get()); + updateContent(); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.WARNING, "Failed to cache content for" + file.getName(), ex); + } + } + } + + @Override + protected void failed() { + super.failed(); + LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException()); + } + + + abstract void saveToCache(X result); + } + + abstract class ImageLoaderTask extends CachedLoaderTask> { + + public ImageLoaderTask(DrawableFile file) { + super(file); + } + + @Override + void saveToCache(Image result) { + synchronized (DrawableUIBase.this) { + imageCache = new SoftReference<>(result); + } + } + } + + class ThumbnailLoaderTask extends ImageLoaderTask { + + public ThumbnailLoaderTask(DrawableFile file) { + super(file); + } + + @Override + Image load() { + return isCancelled() ? null : file.getThumbnail(); + } + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml index 6dfe0d02ed..9fe781ba45 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml @@ -113,7 +113,7 @@ - + diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 6a09ec60d0..a3c66af62e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -221,7 +221,7 @@ public class GroupPane extends BorderPane { private final InvalidationListener filesSyncListener = (observable) -> { final String header = getHeaderString(); - final List fileIds = getGrouping().fileIds(); + final List fileIds = getGroup().fileIds(); Platform.runLater(() -> { slideShowToggle.setDisable(fileIds.isEmpty()); gridView.getItems().setAll(fileIds); @@ -247,8 +247,8 @@ public class GroupPane extends BorderPane { } //assign last selected file or if none first file in group - if (slideShowFileID == null || getGrouping().fileIds().contains(slideShowFileID) == false) { - slideShowPane.setFile(getGrouping().fileIds().get(0)); + if (slideShowFileID == null || getGroup().fileIds().contains(slideShowFileID) == false) { + slideShowPane.setFile(getGroup().fileIds().get(0)); } else { slideShowPane.setFile(slideShowFileID); } @@ -269,7 +269,7 @@ public class GroupPane extends BorderPane { this.scrollToFileID(globalSelectionModel.lastSelectedProperty().get()); } - public DrawableGroup getGrouping() { + public DrawableGroup getGroup() { return grouping.get(); } @@ -278,7 +278,7 @@ public class GroupPane extends BorderPane { menuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent t) { - Set fileIdSet = new HashSet<>(getGrouping().fileIds()); + Set fileIdSet = new HashSet<>(getGroup().fileIds()); new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet); grpCatSplitMenu.setText(cat.getDisplayName()); @@ -293,7 +293,7 @@ public class GroupPane extends BorderPane { menuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent t) { - Set fileIdSet = new HashSet<>(getGrouping().fileIds()); + Set fileIdSet = new HashSet<>(getGroup().fileIds()); new AddDrawableTagAction(controller).addTagsToFiles(tn, "", fileIdSet); grpTagSplitMenu.setText(tn.getDisplayName()); @@ -304,14 +304,14 @@ public class GroupPane extends BorderPane { } private void selectAllFiles() { - globalSelectionModel.clearAndSelectAll(getGrouping().fileIds()); + globalSelectionModel.clearAndSelectAll(getGroup().fileIds()); } /** create the string to display in the group header */ protected String getHeaderString() { - return isNull(getGrouping()) ? "" - : StringUtils.defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- " - + getGrouping().getHashSetHitsCount() + " hash set hits / " + getGrouping().getSize() + " files"; + return isNull(getGroup()) ? "" + : StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- " + + getGroup().getHashSetHitsCount() + " hash set hits / " + getGroup().getSize() + " files"; } ContextMenu getContextMenu() { @@ -580,11 +580,11 @@ public class GroupPane extends BorderPane { * @param grouping the new grouping assigned to this group */ void setViewState(GroupViewState viewState) { - if (nonNull(getGrouping())) { - getGrouping().fileIds().removeListener(filesSyncListener); - } if (isNull(viewState) || isNull(viewState.getGroup())) { + if (nonNull(getGroup())) { + getGroup().fileIds().removeListener(filesSyncListener); + } this.grouping.set(null); Platform.runLater(() -> { @@ -600,15 +600,18 @@ public class GroupPane extends BorderPane { }); } else { - if (this.grouping.get() != viewState.getGroup()) { + if (getGroup() != viewState.getGroup()) { + if (nonNull(getGroup())) { + getGroup().fileIds().removeListener(filesSyncListener); + } this.grouping.set(viewState.getGroup()); - this.getGrouping().fileIds().addListener(filesSyncListener); + getGroup().fileIds().addListener(filesSyncListener); final String header = getHeaderString(); - gridView.getItems().setAll(getGrouping().fileIds()); Platform.runLater(() -> { + gridView.getItems().setAll(getGroup().fileIds()); slideShowToggle.setDisable(gridView.getItems().isEmpty()); groupLabel.setText(header); resetScrollBar(); @@ -687,6 +690,7 @@ public class GroupPane extends BorderPane { @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); + tile.setFile(item); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.fxml index 195600d74d..7e0187d317 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.fxml @@ -13,7 +13,7 @@ - +
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index 9e544c031c..af618441d3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -38,7 +38,6 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.Region; import javafx.scene.text.Text; import javafx.util.Pair; @@ -50,6 +49,7 @@ import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; +import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; @@ -60,9 +60,6 @@ public class MetaDataPane extends DrawableUIBase { private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName()); - @FXML - private ImageView imageView; - @FXML private TableColumn, ? extends Object>, DrawableAttribute> attributeColumn; @@ -72,9 +69,6 @@ public class MetaDataPane extends DrawableUIBase { @FXML private TableColumn, ? extends Object>, String> valueColumn; - @FXML - private BorderPane imageBorder; - public MetaDataPane(ImageGalleryController controller) { super(controller); @@ -155,22 +149,27 @@ public class MetaDataPane extends DrawableUIBase { if (newFileID == null) { Platform.runLater(() -> { imageView.setImage(null); + imageBorder.setCenter(null); tableView.getItems().clear(); getCategoryBorderRegion().setBorder(null); - }); } else { + disposeContent(); updateUI(); + updateContent(); } } + @Override + CachedLoaderTask> getNewImageLoadTask(DrawableFile file) { + return new ThumbnailLoaderTask(file); + } + public void updateUI() { getFile().ifPresent(file -> { - final Image icon = file.getThumbnail(); final ObservableList, ? extends Object>> attributesList = file.getAttributesList(); Platform.runLater(() -> { - imageView.setImage(icon); tableView.getItems().clear(); tableView.getItems().setAll(attributesList); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShow.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.fxml similarity index 96% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShow.fxml rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.fxml index f486303df7..7ef88bc347 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShow.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.fxml @@ -127,7 +127,10 @@
- + +
+ +
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index 412c641ad4..843ce0cafb 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -18,21 +18,27 @@ */ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; +import java.io.IOException; +import java.lang.ref.SoftReference; import java.util.ArrayList; -import java.util.function.Function; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; +import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.MenuItem; import javafx.scene.control.SplitMenuButton; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToolBar; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; import static javafx.scene.input.KeyCode.LEFT; import static javafx.scene.input.KeyCode.RIGHT; @@ -46,6 +52,10 @@ import javafx.scene.layout.CornerRadii; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; +import javafx.scene.media.Media; +import javafx.scene.media.MediaException; +import javafx.scene.media.MediaPlayer; +import javafx.scene.text.Text; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @@ -56,10 +66,9 @@ import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; -import org.sleuthkit.autopsy.imagegallery.datamodel.ImageFile; import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; -import org.sleuthkit.autopsy.imagegallery.gui.MediaControl; +import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -100,31 +109,35 @@ public class SlideShowView extends DrawableTileBase { private ToolBar toolBar; @FXML private BorderPane footer; + private Task mediaTask; SlideShowView(GroupPane gp) { super(gp); - FXMLConstructor.construct(this, "SlideShow.fxml"); + FXMLConstructor.construct(this, "SlideShowView.fxml"); } @FXML @Override protected void initialize() { super.initialize(); - assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert leftButton != null : "fx:id=\"leftButton\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert rightButton != null : "fx:id=\"rightButton\" was not injected: check your FXML file 'SlideShow.fxml'."; - assert tagSplitButton != null : "fx:id=\"tagSplitButton\" was not injected: check your FXML file 'SlideShow.fxml'."; + assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert leftButton != null : "fx:id=\"leftButton\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert rightButton != null : "fx:id=\"rightButton\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert tagSplitButton != null : "fx:id=\"tagSplitButton\" was not injected: check your FXML file 'SlideShowView.fxml'."; Platform.runLater(() -> { HBox.setHgrow(spring, Priority.ALWAYS); spring.setMinWidth(Region.USE_PREF_SIZE); }); + imageView.fitWidthProperty().bind(imageBorder.widthProperty().subtract(CAT_BORDER_WIDTH * 2)); + imageView.fitHeightProperty().bind(heightProperty().subtract(CAT_BORDER_WIDTH * 4).subtract(footer.heightProperty()).subtract(toolBar.heightProperty())); + tagSplitButton.setOnAction((ActionEvent t) -> { try { GuiUtils.createSelTagMenuItem(getController().getTagsManager().getFollowUpTagName(), tagSplitButton, getController()).getOnAction().handle(t); @@ -195,8 +208,8 @@ public class SlideShowView extends DrawableTileBase { getGroupPane().grouping().addListener((Observable observable) -> { syncButtonVisibility(); - if (getGroupPane().getGrouping() != null) { - getGroupPane().getGrouping().fileIds().addListener((Observable observable1) -> { + if (getGroupPane().getGroup() != null) { + getGroupPane().getGroup().fileIds().addListener((Observable observable1) -> { syncButtonVisibility(); }); } @@ -206,7 +219,7 @@ public class SlideShowView extends DrawableTileBase { @ThreadConfined(type = ThreadType.ANY) private void syncButtonVisibility() { try { - final boolean hasMultipleFiles = getGroupPane().getGrouping().fileIds().size() > 1; + final boolean hasMultipleFiles = getGroupPane().getGroup().fileIds().size() > 1; Platform.runLater(() -> { rightButton.setVisible(hasMultipleFiles); leftButton.setVisible(hasMultipleFiles); @@ -221,8 +234,8 @@ public class SlideShowView extends DrawableTileBase { @ThreadConfined(type = ThreadType.JFX) public void stopVideo() { - if (imageBorder.getCenter() instanceof MediaControl) { - ((MediaControl) imageBorder.getCenter()).stopVideo(); + if (imageBorder.getCenter() instanceof VideoPlayer) { + ((VideoPlayer) imageBorder.getCenter()).stopVideo(); } } @@ -239,40 +252,40 @@ public class SlideShowView extends DrawableTileBase { @Override protected void disposeContent() { stopVideo(); - } - @Override - @ThreadConfined(type = ThreadType.UI) - protected void clearContent() { - stopVideo(); - imageBorder.setCenter(null); + super.disposeContent(); + if (mediaTask != null) { + mediaTask.cancel(true); + } + mediaTask = null; + mediaCache = null; } + private SoftReference mediaCache; /** {@inheritDoc } */ @Override - protected Runnable getContentUpdateRunnable() { - - return getFile().map(new Function, Runnable>() { - - @Override - public Runnable apply(DrawableFile file) { - - if (file.isVideo()) { - return () -> { - imageBorder.setCenter(MediaControl.create((VideoFile) file)); - }; + Node getContentNode() { + if (getFile().isPresent() == false) { + mediaCache = null; + return super.getContentNode(); + } else { + DrawableFile file = getFile().get(); + if (file.isVideo()) { + Node mediaNode = (isNull(mediaCache)) ? null : mediaCache.get(); + if (nonNull(mediaNode)) { + return mediaNode; } else { - ImageView imageView = new ImageView(((ImageFile) file).getFullSizeImage()); - imageView.setPreserveRatio(true); - imageView.fitWidthProperty().bind(imageBorder.widthProperty().subtract(CAT_BORDER_WIDTH * 2)); - imageView.fitHeightProperty().bind(heightProperty().subtract(CAT_BORDER_WIDTH * 4).subtract(footer.heightProperty()).subtract(toolBar.heightProperty())); - return () -> { - imageBorder.setCenter(imageView); - }; + if (isNull(mediaTask)) { + mediaTask = new MediaLoadTask(((VideoFile) file)); + new Thread(mediaTask).start(); + } else if (mediaTask.isDone()) { + return null; + } + return getLoadingProgressIndicator(); } } - }).orElse(() -> { - }); + return super.getContentNode(); + } } /** {@inheritDoc } */ @@ -292,12 +305,12 @@ public class SlideShowView extends DrawableTileBase { @ThreadConfined(type = ThreadType.JFX) synchronized private void cycleSlideShowImage(int direction) { stopVideo(); - final int groupSize = getGroupPane().getGrouping().fileIds().size(); + final int groupSize = getGroupPane().getGroup().fileIds().size(); final Integer nextIndex = getFileID().map(fileID -> { - final int currentIndex = getGroupPane().getGrouping().fileIds().indexOf(fileID); + final int currentIndex = getGroupPane().getGroup().fileIds().indexOf(fileID); return (currentIndex + direction + groupSize) % groupSize; }).orElse(0); - setFile(getGroupPane().getGrouping().fileIds().get(nextIndex) + setFile(getGroupPane().getGroup().fileIds().get(nextIndex) ); } @@ -306,7 +319,7 @@ public class SlideShowView extends DrawableTileBase { * of y" */ private String getSupplementalText() { - final ObservableList fileIds = getGroupPane().getGrouping().fileIds(); + final ObservableList fileIds = getGroupPane().getGroup().fileIds(); return getFileID().map(fileID -> " ( " + (fileIds.indexOf(fileID) + 1) + " of " + fileIds.size() + " in group )") .orElse(""); @@ -365,4 +378,49 @@ public class SlideShowView extends DrawableTileBase { }); } } + + @Override + CachedLoaderTask> getNewImageLoadTask(DrawableFile file) { + + return new ImageLoaderTask(file) { + + @Override + Image load() { + return isCancelled() ? null : file.getFullSizeImage(); + } + }; + } + + private class MediaLoadTask extends CachedLoaderTask> { + + public MediaLoadTask(VideoFile file) { + super(file); + } + + @Override + void saveToCache(Node result) { + synchronized (SlideShowView.this) { + mediaCache = new SoftReference<>(result); + } + } + + @Override + Node load() { + try { + final Media media = file.getMedia(); + return new VideoPlayer(new MediaPlayer(media), file); + } catch (MediaException | IOException | OutOfMemoryError ex) { + Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "failed to initialize MediaControl for file " + file.getName(), ex); + + if (file.isDisplayableAsImage()) { + Image fullSizeImage = file.getFullSizeImage(); + Platform.runLater(() -> { + imageView.setImage(fullSizeImage); + }); + return imageView; + } + return new Text(ex.getLocalizedMessage() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action."); + } + } + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 70a2e74a7c..7c1dc087cb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -141,7 +141,8 @@ KeywordSearch.cancelImportMsg=Cancel import KeywordSearch.overwriteListPrompt=Keyword list <{0}> already exists locally, overwrite? KeywordSearch.importOwConflict=Import list conflict KeywordSearch.kwListFailImportMsg=Keyword list not imported -KeywordSearchListsManagementPanel.fileExtensionFilterLbl=Keyword List File +KeywordSearchListsManagementPanel.fileExtensionFilterLbl=Autopsy Keyword List File (xml) +KeywordSearchListsManagementPanel.fileExtensionFilterLb2=Encase Keyword List File (txt) KeywordSearch.listImportFeatureTitle=Keyword List Import KeywordSearchIngestModule.moduleName=Keyword Search KeywordSearchIngestModule.moduleDescription=Performs file indexing and periodic search using keywords and regular expressions in lists. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java index c2038f3629..24c16bb103 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java @@ -210,10 +210,15 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed JFileChooser chooser = new JFileChooser(); - final String[] EXTENSION = new String[]{"xml", "txt"}; //NON-NLS - FileNameExtensionFilter filter = new FileNameExtensionFilter( - NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.fileExtensionFilterLbl"), EXTENSION); - chooser.setFileFilter(filter); + final String[] AUTOPSY_EXTENSIONS = new String[]{"xml"}; //NON-NLS + final String[] ENCASE_EXTENSIONS = new String[]{"txt"}; //NON-NLS + FileNameExtensionFilter autopsyFilter = new FileNameExtensionFilter( + NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.fileExtensionFilterLbl"), AUTOPSY_EXTENSIONS); + FileNameExtensionFilter encaseFilter = new FileNameExtensionFilter( + NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.fileExtensionFilterLb2"), ENCASE_EXTENSIONS); + chooser.addChoosableFileFilter(autopsyFilter); + chooser.addChoosableFileFilter(encaseFilter); + chooser.setAcceptAllFileFilterUsed(false); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); String listName = null; diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 09b8822c3f..7cdc53d7ae 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Wed, 15 Apr 2015 18:11:08 -0400 +#Tue, 28 Jul 2015 13:44:22 -0400 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 3.1.2 +currentVersion=Autopsy 3.1.3 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 4bfb72271d..26b21e9fc3 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Wed, 15 Apr 2015 18:11:08 -0400 +#Tue, 28 Jul 2015 13:44:22 -0400 -CTL_MainWindow_Title=Autopsy 3.1.2 -CTL_MainWindow_Title_No_Project=Autopsy 3.1.2 +CTL_MainWindow_Title=Autopsy 3.1.3 +CTL_MainWindow_Title_No_Project=Autopsy 3.1.3 diff --git a/nbproject/platform.properties b/nbproject/platform.properties index d19032bb98..8a6773633b 100644 --- a/nbproject/platform.properties +++ b/nbproject/platform.properties @@ -1,4 +1,5 @@ branding.token=autopsy +nbjdk.active=JDK_1.8u40x64 # Version of platform that is automatically downloaded # Note build.xml has similar definitions that should be kept in sync (manually) netbeans-plat-version=7.3.1 @@ -13,111 +14,7 @@ cluster.path=\ ${nbplatform.active.dir}/java:\ ${nbplatform.active.dir}/platform disabled.modules=\ - org.apache.tools.ant.module,\ - org.netbeans.api.debugger.jpda,\ - org.netbeans.api.java,\ - org.netbeans.lib.nbjavac,\ - org.netbeans.libs.cglib,\ - org.netbeans.libs.javacapi,\ - org.netbeans.libs.javacimpl,\ - org.netbeans.libs.springframework,\ - org.netbeans.modules.ant.browsetask,\ - org.netbeans.modules.ant.debugger,\ - org.netbeans.modules.ant.freeform,\ - org.netbeans.modules.ant.grammar,\ - org.netbeans.modules.ant.kit,\ - org.netbeans.modules.beans,\ - org.netbeans.modules.classfile,\ - org.netbeans.modules.dbschema,\ - org.netbeans.modules.debugger.jpda,\ - org.netbeans.modules.debugger.jpda.ant,\ - org.netbeans.modules.debugger.jpda.kit,\ - org.netbeans.modules.debugger.jpda.projects,\ - org.netbeans.modules.debugger.jpda.ui,\ - org.netbeans.modules.debugger.jpda.visual,\ - org.netbeans.modules.findbugs.installer,\ - org.netbeans.modules.form,\ - org.netbeans.modules.form.binding,\ - org.netbeans.modules.form.j2ee,\ - org.netbeans.modules.form.kit,\ - org.netbeans.modules.form.nb,\ - org.netbeans.modules.form.refactoring,\ - org.netbeans.modules.hibernate,\ - org.netbeans.modules.hibernatelib,\ - org.netbeans.modules.hudson.ant,\ - org.netbeans.modules.hudson.maven,\ - org.netbeans.modules.i18n,\ - org.netbeans.modules.i18n.form,\ - org.netbeans.modules.j2ee.core.utilities,\ - org.netbeans.modules.j2ee.eclipselink,\ - org.netbeans.modules.j2ee.eclipselinkmodelgen,\ - org.netbeans.modules.j2ee.jpa.refactoring,\ - org.netbeans.modules.j2ee.jpa.verification,\ - org.netbeans.modules.j2ee.metadata,\ - org.netbeans.modules.j2ee.metadata.model.support,\ - org.netbeans.modules.j2ee.persistence,\ - org.netbeans.modules.j2ee.persistence.kit,\ - org.netbeans.modules.j2ee.persistenceapi,\ - org.netbeans.modules.java.api.common,\ - org.netbeans.modules.java.debug,\ - org.netbeans.modules.java.editor,\ - org.netbeans.modules.java.editor.lib,\ - org.netbeans.modules.java.examples,\ - org.netbeans.modules.java.freeform,\ - org.netbeans.modules.java.guards,\ - org.netbeans.modules.java.helpset,\ - org.netbeans.modules.java.hints,\ - org.netbeans.modules.java.hints.declarative,\ - org.netbeans.modules.java.hints.declarative.test,\ - org.netbeans.modules.java.hints.legacy.spi,\ - org.netbeans.modules.java.hints.test,\ - org.netbeans.modules.java.hints.ui,\ - org.netbeans.modules.java.j2seplatform,\ - org.netbeans.modules.java.j2seproject,\ - org.netbeans.modules.java.kit,\ - org.netbeans.modules.java.lexer,\ - org.netbeans.modules.java.navigation,\ - org.netbeans.modules.java.platform,\ - org.netbeans.modules.java.preprocessorbridge,\ - org.netbeans.modules.java.project,\ - org.netbeans.modules.java.source,\ - org.netbeans.modules.java.source.ant,\ - org.netbeans.modules.java.source.queries,\ - org.netbeans.modules.java.source.queriesimpl,\ - org.netbeans.modules.java.sourceui,\ - org.netbeans.modules.java.testrunner,\ - org.netbeans.modules.javadoc,\ - org.netbeans.modules.javawebstart,\ + org.netbeans.modules.jellytools.java,\ org.netbeans.modules.junit,\ - org.netbeans.modules.maven,\ - org.netbeans.modules.maven.checkstyle,\ - org.netbeans.modules.maven.coverage,\ - org.netbeans.modules.maven.embedder,\ - org.netbeans.modules.maven.grammar,\ - org.netbeans.modules.maven.graph,\ - org.netbeans.modules.maven.hints,\ - org.netbeans.modules.maven.indexer,\ - org.netbeans.modules.maven.junit,\ - org.netbeans.modules.maven.kit,\ - org.netbeans.modules.maven.model,\ - org.netbeans.modules.maven.osgi,\ - org.netbeans.modules.maven.persistence,\ - org.netbeans.modules.maven.refactoring,\ - org.netbeans.modules.maven.repository,\ - org.netbeans.modules.maven.search,\ - org.netbeans.modules.maven.spring,\ - org.netbeans.modules.projectimport.eclipse.core,\ - org.netbeans.modules.projectimport.eclipse.j2se,\ - org.netbeans.modules.refactoring.java,\ - org.netbeans.modules.spellchecker.bindings.java,\ - org.netbeans.modules.spring.beans,\ - org.netbeans.modules.testng,\ - org.netbeans.modules.testng.ant,\ - org.netbeans.modules.testng.maven,\ - org.netbeans.modules.websvc.jaxws21,\ - org.netbeans.modules.websvc.jaxws21api,\ - org.netbeans.modules.websvc.saas.codegen.java,\ - org.netbeans.modules.xml.jaxb,\ - org.netbeans.modules.xml.tools.java,\ - org.netbeans.spi.java.hints + org.netbeans.modules.whitelist diff --git a/nbproject/project.properties b/nbproject/project.properties index d46c65d75a..f80c2ec8a8 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -10,6 +10,7 @@ app.version=3.1.3 #build.type=RELEASE build.type=DEVELOPMENT +project.org.sleuthkit.autopsy.imagegallery=ImageGallery update_versions=false #custom JVM options #Note: can be higher on 64 bit systems, should be in sync with build.xml @@ -27,7 +28,8 @@ modules=\ ${project.org.sleuthkit.autopsy.testing}:\ ${project.org.sleuthkit.autopsy.thunderbirdparser}:\ ${project.org.sleuthkit.autopsy.core}:\ - ${project.org.sleuthkit.autopsy.corelibs} + ${project.org.sleuthkit.autopsy.corelibs}:\ + ${project.org.sleuthkit.autopsy.imagegallery} project.org.sleuthkit.autopsy.core=Core project.org.sleuthkit.autopsy.corelibs=CoreLibs project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch diff --git a/test/script/regression.py b/test/script/regression.py index 60dcc6ba2e..32b714b058 100755 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -870,7 +870,9 @@ class TestResultsDiffer(object): diff_path = os.path.splitext(os.path.basename(output_file))[0] diff_path += "-Diff.txt" diff_file = codecs.open(diff_path, "wb", "utf_8") - dffcmdlst = ["diff", output_file, gold_file] + + # Gold needs to be passed in before output. + dffcmdlst = ["diff", gold_file, output_file] subprocess.call(dffcmdlst, stdout = diff_file) Errors.add_errors_out(diff_path) @@ -902,7 +904,8 @@ class TestResultsDiffer(object): gold_report_path = test_data.get_html_report_path(DBType.GOLD) output_report_path = test_data.get_html_report_path(DBType.OUTPUT) try: - (subprocess.check_output(['diff', '-r', '-N', '-x', '*.png', '-x', '*.ico', '--ignore-matching-lines', + # Ensure gold is passed before output + (subprocess.check_output(["diff", '-r', '-N', '-x', '*.png', '-x', '*.ico', '--ignore-matching-lines', 'HTML Report Generated on \|Autopsy Report for case \|Case:\|Case Number:' '\|Examiner:', gold_report_path, output_report_path])) print_report("", "REPORT COMPARISON", "The test reports matched the gold reports") diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py index 6a8f5f6727..7468096223 100755 --- a/test/script/tskdbdiff.py +++ b/test/script/tskdbdiff.py @@ -139,6 +139,7 @@ class TskDbDiff(object): # If they are different, invoke 'diff' diff_file = codecs.open(diff_path, "wb", "utf_8") + # Gold needs to be passed in as 1st arg and output as 2nd dffcmdlst = ["diff", gold_file, output_file] subprocess.call(dffcmdlst, stdout = diff_file)