Merge remote-tracking branch 'upstream/develop' into double_click_activate_tile_view

Conflicts:
	ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java
	ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java
This commit is contained in:
jmillman 2015-07-28 14:02:55 -04:00
commit 5c4bd438e8
60 changed files with 1582 additions and 1252 deletions

View File

@ -223,6 +223,10 @@
<runtime-relative-path>ext/StixLib.jar</runtime-relative-path> <runtime-relative-path>ext/StixLib.jar</runtime-relative-path>
<binary-origin>release/modules/ext/StixLib.jar</binary-origin> <binary-origin>release/modules/ext/StixLib.jar</binary-origin>
</class-path-extension> </class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/opencv-248.jar</runtime-relative-path>
<binary-origin>release/modules/ext/opencv-248.jar</binary-origin>
</class-path-extension>
<class-path-extension> <class-path-extension>
<runtime-relative-path>ext/sqlite-jdbc-3.7.15-M1.jar</runtime-relative-path> <runtime-relative-path>ext/sqlite-jdbc-3.7.15-M1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sqlite-jdbc-3.7.15-M1.jar</binary-origin> <binary-origin>release/modules/ext/sqlite-jdbc-3.7.15-M1.jar</binary-origin>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -21,9 +21,8 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.CardLayout; import java.awt.CardLayout;
import java.awt.Component; import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
import java.util.Arrays; import java.util.List;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.logging.Level; 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.ServiceProvider;
import org.openide.util.lookup.ServiceProviders; import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile; 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 { public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
private static final Set<String> AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
private AbstractFile lastFile; private AbstractFile lastFile;
//UI //UI
private final MediaViewVideoPanel videoPanel; private final MediaViewVideoPanel videoPanel;
private final boolean videoPanelInited;
private final SortedSet<String> videoExtensions; // get them from the panel private final SortedSet<String> videoExtensions; // get them from the panel
private final SortedSet<String> videoMimes; private final SortedSet<String> videoMimes;
private final MediaViewImagePanel imagePanel; private final MediaViewImagePanel imagePanel;
private final boolean videoPanelInited;
private final boolean imagePanelInited; private final boolean imagePanelInited;
private final SortedSet<String> imageExtensions; // get them from the panel
private final SortedSet<String> imageMimes;
private static final String IMAGE_VIEWER_LAYER = "IMAGE"; //NON-NLS private static final String IMAGE_VIEWER_LAYER = "IMAGE"; //NON-NLS
private static final String VIDEO_VIEWER_LAYER = "VIDEO"; //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 // get the right panel for our platform
videoPanel = MediaViewVideoPanel.createVideoPanel(); videoPanel = MediaViewVideoPanel.createVideoPanel();
videoPanelInited = videoPanel.isInited(); videoPanelInited = videoPanel.isInited();
videoExtensions = new TreeSet<>(Arrays.asList(videoPanel.getExtensions())); videoExtensions = new TreeSet<>(videoPanel.getExtensionsList());
videoMimes = new TreeSet<>(videoPanel.getMimeTypes()); videoMimes = new TreeSet<>(videoPanel.getMimeTypes());
imagePanel = new MediaViewImagePanel(); imagePanel = new MediaViewImagePanel();
imagePanelInited = imagePanel.isInited(); imagePanelInited = imagePanel.isInited();
imageExtensions = new TreeSet<>(imagePanel.getExtensionsList());
imageMimes = new TreeSet<>(imagePanel.getMimeTypes());
customizeComponents(); 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() { private void customizeComponents() {
@ -125,12 +127,12 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
final Dimension dims = DataContentViewerMedia.this.getSize(); final Dimension dims = DataContentViewerMedia.this.getSize();
//logger.info("setting node on media viewer"); //NON-NLS //logger.info("setting node on media viewer"); //NON-NLS
if (imagePanelInited && isImageSupported(file)) { if (videoPanelInited && videoPanel.isSupported(file)) {
imagePanel.showImageFx(file, dims);
this.showVideoPanel(false);
} else if (videoPanelInited && isVideoSupported(file)) {
videoPanel.setupVideo(file, dims); videoPanel.setupVideo(file, dims);
this.showVideoPanel(true); this.showVideoPanel(true);
} else if (imagePanelInited && imagePanel.isSupported(file)) {
imagePanel.showImageFx(file, dims);
this.showVideoPanel(false);
} }
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "Exception while setting node", e); //NON-NLS logger.log(Level.SEVERE, "Exception while setting node", e); //NON-NLS
@ -186,25 +188,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
* @return True if a video file that can be displayed * @return True if a video file that can be displayed
*/ */
private boolean isVideoSupported(AbstractFile file) { private boolean isVideoSupported(AbstractFile file) {
String extension = file.getNameExtension(); if (null == file || file.getSize() == 0) {
//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;
}
}
}
return false; return false;
} }
return videoPanel.isSupported(file);
}
/** /**
* is the given file an image that we can display? * is the given file an image that we can display?
@ -215,7 +203,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
*/ */
private boolean isImageSupported(AbstractFile file) { private boolean isImageSupported(AbstractFile file) {
return ImageUtils.thumbnailSupported(file); if (null == file || file.getSize() == 0) {
return false;
}
return imagePanel.isSupported(file);
} }
@Override @Override
@ -256,4 +248,43 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
return 7; return 7;
} }
} }
interface MediaViewPanel {
/**
* @return supported mime types
*/
List<String> getMimeTypes();
/**
* returns supported extensions (each starting with .)
*
* @return
*/
List<String> getExtensionsList();
default boolean isSupported(AbstractFile file) {
SortedSet<String> 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;
}
}
} }

View File

@ -16,7 +16,8 @@
<Layout> <Layout>
<DimensionLayout dim="0"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Component id="thumbnailScrollPanel" pref="642" max="32767" attributes="0"/> <Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
@ -43,7 +44,14 @@
<Component id="thumbnailSizeComboBox" min="-2" max="-2" attributes="0"/> <Component id="thumbnailSizeComboBox" min="-2" max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
<EmptySpace max="32767" attributes="0"/> </Group>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="iconView" pref="563" max="32767" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -66,8 +74,8 @@
<Component id="thumbnailSizeComboBox" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="thumbnailSizeComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="thumbnailScrollPanel" max="32767" attributes="0"/> <Component id="iconView" pref="330" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="filePathLabel" min="-2" max="-2" attributes="0"/> <Component id="filePathLabel" min="-2" max="-2" attributes="0"/>
</Group> </Group>
@ -75,18 +83,6 @@
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
<SubComponents> <SubComponents>
<Container class="javax.swing.JScrollPane" name="thumbnailScrollPanel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[582, 348]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new IconView();"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
<Component class="javax.swing.JLabel" name="pageLabel"> <Component class="javax.swing.JLabel" name="pageLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -213,5 +209,7 @@
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/> <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues> </AuxValues>
</Component> </Component>
<Component class="org.openide.explorer.view.IconView" name="iconView">
</Component>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -27,9 +27,6 @@ import java.util.Arrays;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
@ -37,7 +34,6 @@ import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.DialogDisplayer; import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor; import org.openide.NotifyDescriptor;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.IconView;
import org.openide.nodes.AbstractNode; import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
@ -45,9 +41,10 @@ import org.openide.nodes.NodeEvent;
import org.openide.nodes.NodeListener; import org.openide.nodes.NodeListener;
import org.openide.nodes.NodeMemberEvent; import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent; 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.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -93,7 +90,7 @@ import org.sleuthkit.datamodel.TskCoreException;
private void initialize() { private void initialize() {
initComponents(); initComponents();
((IconView) thumbnailScrollPanel).setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
curPage = -1; curPage = -1;
@ -110,7 +107,6 @@ import org.sleuthkit.datamodel.TskCoreException;
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
thumbnailScrollPanel = new IconView();
pageLabel = new javax.swing.JLabel(); pageLabel = new javax.swing.JLabel();
pagesLabel = new javax.swing.JLabel(); pagesLabel = new javax.swing.JLabel();
pagePrevButton = new javax.swing.JButton(); pagePrevButton = new javax.swing.JButton();
@ -122,32 +118,31 @@ import org.sleuthkit.datamodel.TskCoreException;
goToPageLabel = new javax.swing.JLabel(); goToPageLabel = new javax.swing.JLabel();
goToPageField = new javax.swing.JTextField(); goToPageField = new javax.swing.JTextField();
thumbnailSizeComboBox = new javax.swing.JComboBox<>(); thumbnailSizeComboBox = new javax.swing.JComboBox<>();
iconView = new org.openide.explorer.view.IconView();
thumbnailScrollPanel.setPreferredSize(new java.awt.Dimension(582, 348));
pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageLabel.text")); // NOI18N 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 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.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.setMargin(new java.awt.Insets(2, 0, 2, 0));
pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23)); 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() { pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
pagePrevButtonActionPerformed(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.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.setMargin(new java.awt.Insets(2, 0, 2, 0));
pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23)); pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
pageNextButton.setMinimumSize(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() { pageNextButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
pageNextButtonActionPerformed(evt); pageNextButtonActionPerformed(evt);
@ -171,10 +166,7 @@ import org.sleuthkit.datamodel.TskCoreException;
} }
}); });
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "Small Thumbnails", "Medium Thumbnails", "Large Thumbnails" }));
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.smallThumbnails"),
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.mediumThumbnails"),
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.largeThumbnails") }));
thumbnailSizeComboBox.addActionListener(new java.awt.event.ActionListener() { thumbnailSizeComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
thumbnailSizeComboBoxActionPerformed(evt); thumbnailSizeComboBoxActionPerformed(evt);
@ -185,7 +177,8 @@ import org.sleuthkit.datamodel.TskCoreException;
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 642, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addContainerGap() .addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -209,8 +202,12 @@ import org.sleuthkit.datamodel.TskCoreException;
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(thumbnailSizeComboBox, 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))))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 563, Short.MAX_VALUE)
.addGap(0, 0, 0)))
.addContainerGap())
); );
layout.setVerticalGroup( layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -228,8 +225,8 @@ import org.sleuthkit.datamodel.TskCoreException;
.addComponent(goToPageLabel) .addComponent(goToPageLabel)
.addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .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))) .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addGap(0, 0, 0) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 330, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(filePathLabel)) .addComponent(filePathLabel))
); );
@ -283,6 +280,7 @@ import org.sleuthkit.datamodel.TskCoreException;
private javax.swing.JLabel filePathLabel; private javax.swing.JLabel filePathLabel;
private javax.swing.JTextField goToPageField; private javax.swing.JTextField goToPageField;
private javax.swing.JLabel goToPageLabel; private javax.swing.JLabel goToPageLabel;
private org.openide.explorer.view.IconView iconView;
private javax.swing.JLabel imagesLabel; private javax.swing.JLabel imagesLabel;
private javax.swing.JLabel imagesRangeLabel; private javax.swing.JLabel imagesRangeLabel;
private javax.swing.JLabel pageLabel; private javax.swing.JLabel pageLabel;
@ -290,7 +288,6 @@ import org.sleuthkit.datamodel.TskCoreException;
private javax.swing.JLabel pageNumLabel; private javax.swing.JLabel pageNumLabel;
private javax.swing.JButton pagePrevButton; private javax.swing.JButton pagePrevButton;
private javax.swing.JLabel pagesLabel; private javax.swing.JLabel pagesLabel;
private javax.swing.JScrollPane thumbnailScrollPanel;
private javax.swing.JComboBox<String> thumbnailSizeComboBox; private javax.swing.JComboBox<String> thumbnailSizeComboBox;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
@ -299,8 +296,15 @@ import org.sleuthkit.datamodel.TskCoreException;
if (selectedNode == null) { if (selectedNode == null) {
return false; return false;
} }
Children ch = selectedNode.getChildren();
for (Node n : ch.getNodes()) {
if (ThumbnailViewChildren.isSupported(n)) {
return true; return true;
} }
}
return false;
}
@Override @Override
public void setNode(Node givenNode) { public void setNode(Node givenNode) {
@ -318,8 +322,7 @@ import org.sleuthkit.datamodel.TskCoreException;
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node em.setRootContext(emptyNode); // make empty node
IconView iv = ((IconView) this.thumbnailScrollPanel); iconView.setBackground(Color.BLACK);
iv.setBackground(Color.BLACK);
} }
} finally { } finally {
this.setCursor(null); this.setCursor(null);
@ -348,8 +351,8 @@ import org.sleuthkit.datamodel.TskCoreException;
@Override @Override
public void clearComponent() { public void clearComponent() {
this.thumbnailScrollPanel.removeAll(); this.iconView.removeAll();
this.thumbnailScrollPanel = null; this.iconView = null;
super.clearComponent(); super.clearComponent();
} }
@ -374,8 +377,7 @@ import org.sleuthkit.datamodel.TskCoreException;
int newPage; int newPage;
try { try {
newPage = Integer.parseInt(pageNumText); newPage = Integer.parseInt(pageNumText);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
//ignore input //ignore input
return; return;
} }
@ -433,8 +435,8 @@ import org.sleuthkit.datamodel.TskCoreException;
try { try {
get(); get();
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
NotifyDescriptor d = NotifyDescriptor d
new NotifyDescriptor.Message( = new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg",
ex.getMessage()), ex.getMessage()),
NotifyDescriptor.ERROR_MESSAGE); NotifyDescriptor.ERROR_MESSAGE);
@ -534,7 +536,6 @@ import org.sleuthkit.datamodel.TskCoreException;
em.setExploredContext(pageNode); em.setExploredContext(pageNode);
} }
updateControls(); updateControls();
} }
@ -556,6 +557,7 @@ import org.sleuthkit.datamodel.TskCoreException;
} }
private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener {
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { 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); AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
if (af == null) { if (af == null) {
filePathLabel.setText(""); filePathLabel.setText("");
} } else {
else {
try { try {
String uPath = af.getUniquePath(); String uPath = af.getUniquePath();
filePathLabel.setText(uPath); filePathLabel.setText(uPath);
filePathLabel.setToolTipText(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 logger.log(Level.WARNING, "Could not get unique path for content: {0}", af.getName()); //NON-NLS
} }
} }
} } else {
else {
filePathLabel.setText(""); filePathLabel.setText("");
} }
} } finally {
finally {
setCursor(null); setCursor(null);
} }
} }
} }
} }
} }

View File

@ -16,8 +16,35 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"/> <Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jFXPanel" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jFXPanel" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javafx.embed.swing.JFXPanel" name="jFXPanel">
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="400" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="300" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Container>
</SubComponents>
</Form> </Form>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,7 +18,9 @@
*/ */
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import com.google.common.io.Files;
import java.awt.Dimension; import java.awt.Dimension;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
@ -26,11 +28,8 @@ import java.util.List;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.value.ChangeListener; import javafx.concurrent.Task;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.JFXPanel;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.geometry.Insets; import javafx.geometry.Insets;
@ -55,17 +54,15 @@ import static javafx.scene.media.MediaPlayer.Status.STOPPED;
import javafx.scene.media.MediaView; import javafx.scene.media.MediaView;
import javafx.util.Duration; import javafx.util.Duration;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders; import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -83,51 +80,30 @@ public class FXVideoPanel extends MediaViewVideoPanel {
// for Javafx supported formats // 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 String[] EXTENSIONS = new String[]{".m4v", ".fxm", ".flv", ".m3u8", ".mp4", ".aif", ".aiff", ".mp3", "m4a", ".wav"}; //NON-NLS
private static final List<String> 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 List<String> 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; private boolean fxInited = false;
// FX Components
private MediaPane mediaPane; private MediaPane mediaPane;
// Current media content representations
private AbstractFile currentFile; private AbstractFile currentFile;
// FX UI Components
private JFXPanel videoComponent;
/**
* Creates new form MediaViewVideoPanel
*/
public FXVideoPanel() { public FXVideoPanel() {
fxInited = Installer.isJavaFxInited(); fxInited = Installer.isJavaFxInited();
initComponents(); initComponents();
if (fxInited) { if (fxInited) {
setupFx(); Platform.runLater(() -> {
}
}
public JPanel getVideoPanel() {
return this;
}
private void setupFx() {
Platform.runLater(new Runnable() {
@Override
public void run() {
videoComponent = new JFXPanel();
mediaPane = new MediaPane(); mediaPane = new MediaPane();
Scene fxScene = new Scene(mediaPane); Scene fxScene = new Scene(mediaPane);
videoComponent.setScene(fxScene); jFXPanel.setScene(fxScene);
});
}
}
SwingUtilities.invokeLater(new Runnable() { @Deprecated
@Override public JPanel getVideoPanel() {
public void run() { return this;
add(videoComponent);
}
});
}
});
} }
@Override @Override
@ -148,50 +124,33 @@ public class FXVideoPanel extends MediaViewVideoPanel {
removeAll(); removeAll();
return; return;
} }
mediaPane.setFit(dims);
String path = ""; String path = "";
try { try {
path = file.getUniquePath(); path = file.getUniquePath();
} catch (TskCoreException ex) { } 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.setInfoLabelText(path);
mediaPane.setInfoLabelToolTipText(path); mediaPane.setInfoLabelToolTipText(path);
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); final File tempFile = VideoUtils.getTempVideoFile(currentFile);
em.execute();
new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
mediaPane.setFit(dims);
} }
@Override @Override
void reset() { void reset() {
Platform.runLater(new Runnable() { Platform.runLater(() -> {
@Override
public void run() {
if (mediaPane != null) { if (mediaPane != null) {
mediaPane.reset(); mediaPane.reset();
} }
}
}); });
currentFile = null; 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. * 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 * WARNING: Do NOT modify this code. The content of this method is always
@ -201,10 +160,34 @@ public class FXVideoPanel extends MediaViewVideoPanel {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
jFXPanel = new javafx.embed.swing.JFXPanel();
setBackground(new java.awt.Color(0, 0, 0)); 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)
);
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javafx.embed.swing.JFXPanel jFXPanel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
@Override @Override
@ -212,134 +195,32 @@ public class FXVideoPanel extends MediaViewVideoPanel {
return fxInited; 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<Object, Void> {
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 class MediaPane extends BorderPane {
private MediaPlayer mediaPlayer; private MediaPlayer mediaPlayer;
private MediaView mediaView; private final MediaView mediaView;
/** The Duration of the media. * */ /** The Duration of the media. * */
private Duration duration; private Duration duration;
/** The container for the media controls. * */ /** The container for the media controls. * */
private HBox mediaTools; private final HBox mediaTools;
/** The container for the media video output. * */ /** 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; private int totalHours;
@ -347,22 +228,7 @@ public class FXVideoPanel extends MediaViewVideoPanel {
private int totalSeconds; private int totalSeconds;
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS private final 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 static final String PLAY_TEXT = ""; private static final String PLAY_TEXT = "";
@ -409,22 +275,6 @@ public class FXVideoPanel extends MediaViewVideoPanel {
setProgressActionListeners(); 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. * Reset this MediaPane.
* *
@ -447,12 +297,9 @@ public class FXVideoPanel extends MediaViewVideoPanel {
* @param text * @param text
*/ */
public void setInfoLabelText(final String text) { public void setInfoLabelText(final String text) {
logger.log(Level.INFO, "Setting Info Label Text: " + text); //NON-NLS logger.log(Level.INFO, "Setting Info Label Text: {0}", text); //NON-NLS
Platform.runLater(new Runnable() { Platform.runLater(() -> {
@Override
public void run() {
infoLabel.setText(text); infoLabel.setText(text);
}
}); });
} }
@ -462,14 +309,11 @@ public class FXVideoPanel extends MediaViewVideoPanel {
* @param dims the current dimensions of the DataContentViewer * @param dims the current dimensions of the DataContentViewer
*/ */
public void setFit(final Dimension dims) { public void setFit(final Dimension dims) {
Platform.runLater(new Runnable() { Platform.runLater(() -> {
@Override
public void run() {
setPrefSize(dims.getWidth(), dims.getHeight()); setPrefSize(dims.getWidth(), dims.getHeight());
// Set the Video output to fit the size allocated for it. give an // Set the Video output to fit the size allocated for it. give an
// extra few px to ensure the info label will be shown // extra few px to ensure the info label will be shown
mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight()); mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
}
}); });
} }
@ -498,7 +342,7 @@ public class FXVideoPanel extends MediaViewVideoPanel {
mediaPlayer.play(); mediaPlayer.play();
break; break;
default: 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. // If the MediaPlayer is in an unexpected state, stop playback.
mediaPlayer.stop(); mediaPlayer.stop();
setInfoLabelText(NbBundle.getMessage(this.getClass(), setInfoLabelText(NbBundle.getMessage(this.getClass(),
@ -508,20 +352,15 @@ public class FXVideoPanel extends MediaViewVideoPanel {
} }
}); });
stopButton.setOnAction(new EventHandler<ActionEvent>() { stopButton.setOnAction((ActionEvent e) -> {
@Override
public void handle(ActionEvent e) {
if (mediaPlayer == null) { if (mediaPlayer == null) {
return; return;
} }
mediaPlayer.stop(); mediaPlayer.stop();
}
}); });
progressSlider.valueProperty().addListener(new InvalidationListener() { progressSlider.valueProperty().addListener((Observable o) -> {
@Override
public void invalidated(Observable o) {
if (mediaPlayer == null) { if (mediaPlayer == null) {
return; return;
} }
@ -529,7 +368,6 @@ public class FXVideoPanel extends MediaViewVideoPanel {
if (progressSlider.isValueChanging()) { if (progressSlider.isValueChanging()) {
mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0)); mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
} }
}
}); });
} }
@ -557,13 +395,21 @@ public class FXVideoPanel extends MediaViewVideoPanel {
Media media = new Media(mediaUri); Media media = new Media(mediaUri);
MediaPlayer player = new MediaPlayer(media); MediaPlayer player = new MediaPlayer(media);
player.setOnReady(READY_LISTENER); player.setOnReady(new ReadyListener());
player.setOnPaused(NOT_PLAY_LISTENER); final Runnable pauseListener = () -> {
player.setOnStopped(NOT_PLAY_LISTENER); pauseButton.setText(PLAY_TEXT);
player.setOnPlaying(PLAY_LISTENER); };
player.setOnEndOfMedia(END_LISTENER); 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; return player;
} }
@ -616,29 +462,14 @@ public class FXVideoPanel extends MediaViewVideoPanel {
String durationStr = String.format(durationFormat, String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds, elapsedHours, elapsedMinutes, elapsedSeconds,
totalHours, totalMinutes, totalSeconds); totalHours, totalMinutes, totalSeconds);
setProgressLabelText(durationStr); Platform.runLater(() -> {
} progressLabel.setText(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);
}
}); });
} }
private void setInfoLabelToolTipText(final String text) { private void setInfoLabelToolTipText(final String text) {
Platform.runLater(new Runnable() { Platform.runLater(() -> {
@Override
public void run() {
infoLabel.setTooltip(new Tooltip(text)); infoLabel.setTooltip(new Tooltip(text));
}
}); });
} }
@ -692,38 +523,105 @@ public class FXVideoPanel extends MediaViewVideoPanel {
} }
/** /**
* Responds to changes in the MediaPlayer currentTime property. * 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 Task<Long> {
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;
}
/**
* Get the URI of the media file.
* *
* Updates the progress slider and label with the current Time. * @return the URI of the media file.
*/ */
private class TimeListener implements ChangeListener<Duration> { public String getMediaUri() {
return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
}
@Override @Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) { protected Long call() throws Exception {
updateSlider(newValue); if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
updateTime(newValue); progress = ProgressHandleFactory.createHandle(
} NbBundle.getMessage(this.getClass(),
} "FXVideoPanel.progress.bufferingFile",
sourceFile.getName()
),
() -> ExtractMedia.this.cancel(true));
/** Platform.runLater(() -> {
* Triggered when MediaPlayer State changes to PAUSED or Stopped. progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.buffering"));
*/ });
private class NotPlayListener implements Runnable {
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 @Override
public void run() { protected void failed() {
pauseButton.setText(PLAY_TEXT); super.failed();
onDone();
} }
}
/**
* Triggered when MediaPlayer State changes to PLAYING.
*/
private class PlayListener implements Runnable {
@Override @Override
public void run() { protected void succeeded() {
pauseButton.setText(PAUSE_TEXT); 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 @Override
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception { public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
// //What is/was the point of this method /interface.
// try {
// List<VideoFrame> 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;
// }
return null; 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<VideoFrame> getFrames(int numFrames) {
// logger.log(Level.INFO, "in get frames");
// List<VideoFrame> frames = new ArrayList<VideoFrame>(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 @Override
public String[] getExtensions() { public String[] getExtensions() {
return EXTENSIONS; return EXTENSIONS.clone();
} }
@Override @Override

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.Dimension;
import java.awt.Image; import java.awt.Image;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
@ -41,7 +42,6 @@ import javax.swing.JSlider;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.gstreamer.ClockTime; import org.gstreamer.ClockTime;
import org.gstreamer.Gst; import org.gstreamer.Gst;
import org.gstreamer.GstException; import org.gstreamer.GstException;
@ -52,23 +52,22 @@ import org.gstreamer.elements.RGBDataSink;
import org.gstreamer.swing.VideoComponent; import org.gstreamer.swing.VideoComponent;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders; import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
@ServiceProviders(value = { @ServiceProviders(value = {
@ServiceProvider(service = FrameCapture.class) @ServiceProvider(service = FrameCapture.class)
}) })
public class GstVideoPanel extends MediaViewVideoPanel { 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<String> 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 List<String> 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
@ -129,14 +128,12 @@ public class GstVideoPanel extends MediaViewVideoPanel {
progressSlider.setEnabled(false); // disable slider; enable after user plays vid progressSlider.setEnabled(false); // disable slider; enable after user plays vid
progressSlider.setValue(0); progressSlider.setValue(0);
progressSlider.addChangeListener(new ChangeListener() { progressSlider.addChangeListener((ChangeEvent e) -> {
/** /**
* Should always try to synchronize any call to * Should always try to synchronize any call to
* progressSlider.setValue() to avoid a different thread * progressSlider.setValue() to avoid a different thread
* changing playbin while stateChanged() is processing * changing playbin while stateChanged() is processing
*/ */
@Override
public void stateChanged(ChangeEvent e) {
int time = progressSlider.getValue(); int time = progressSlider.getValue();
synchronized (playbinLock) { synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) { if (gstPlaybin2 != null && !autoTracking) {
@ -154,7 +151,6 @@ public class GstVideoPanel extends MediaViewVideoPanel {
gstPlaybin2.setState(orig); gstPlaybin2.setState(orig);
} }
} }
}
}); });
} }
@ -207,7 +203,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
pauseButton.setEnabled(true); pauseButton.setEnabled(true);
progressSlider.setEnabled(true); progressSlider.setEnabled(true);
java.io.File ioFile = getJFile(file); java.io.File ioFile = VideoUtils.getTempVideoFile(file);
gstVideoComponent = new VideoComponent(); gstVideoComponent = new VideoComponent();
synchronized (playbinLock) { synchronized (playbinLock) {
@ -219,7 +215,6 @@ public class GstVideoPanel extends MediaViewVideoPanel {
videoPanel.removeAll(); videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(gstVideoComponent); videoPanel.add(gstVideoComponent);
@ -239,11 +234,8 @@ public class GstVideoPanel extends MediaViewVideoPanel {
void reset() { void reset() {
// reset the progress label text on the event dispatch thread // reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(() -> {
@Override
public void run() {
progressLabel.setText(""); progressLabel.setText("");
}
}); });
if (!isInited()) { if (!isInited()) {
@ -281,27 +273,13 @@ public class GstVideoPanel extends MediaViewVideoPanel {
currentFile = null; 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 * @param numFrames the number of frames to capture. These frames will be
* captured at successive intervals given by durationOfVideo/numFrames. If * captured at successive intervals given by durationOfVideo/numFrames. If
* this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one * this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one
* frame will be captured and returned. * frame will be captured and returned.
*
* @return a List of VideoFrames representing the captured frames. * @return a List of VideoFrames representing the captured frames.
*/ */
@Override @Override
@ -557,9 +535,10 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return; return;
} }
} else if (state.equals(State.READY)) { } else if (state.equals(State.READY)) {
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); final File tempVideoFile = VideoUtils.getTempVideoFile(currentFile);
em.execute();
em.getExtractedBytes(); new ExtractMedia(currentFile, tempVideoFile).execute();
} }
} }
}//GEN-LAST:event_pauseButtonActionPerformed }//GEN-LAST:event_pauseButtonActionPerformed
@ -575,11 +554,10 @@ public class GstVideoPanel extends MediaViewVideoPanel {
private class VideoProgressWorker extends SwingWorker<Object, Object> { private class VideoProgressWorker extends SwingWorker<Object, Object> {
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 long millisElapsed = 0;
private final long INTER_FRAME_PERIOD_MS = 20; private final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50; private final long END_TIME_MARGIN_MS = 50;
private boolean hadError = false;
private boolean isPlayBinReady() { private boolean isPlayBinReady() {
synchronized (playbinLock) { synchronized (playbinLock) {
@ -626,8 +604,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
// enable the slider // enable the slider
progressSlider.setEnabled(true); progressSlider.setEnabled(true);
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; ClockTime pos;
ClockTime pos = null;
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) { while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
synchronized (playbinLock) { synchronized (playbinLock) {
@ -637,11 +614,11 @@ public class GstVideoPanel extends MediaViewVideoPanel {
// pick out the elapsed hours, minutes, seconds // pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000; long secondsElapsed = millisElapsed / 1000;
elapsedHours = (int) secondsElapsed / 3600; int elapsedHours = (int) secondsElapsed / 3600;
secondsElapsed -= elapsedHours * 3600; secondsElapsed -= elapsedHours * 3600;
elapsedMinutes = (int) secondsElapsed / 60; int elapsedMinutes = (int) secondsElapsed / 60;
secondsElapsed -= elapsedMinutes * 60; secondsElapsed -= elapsedMinutes * 60;
elapsedSeconds = (int) secondsElapsed; int elapsedSeconds = (int) secondsElapsed;
String durationStr = String.format(durationFormat, String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds, elapsedHours, elapsedMinutes, elapsedSeconds,
@ -667,7 +644,6 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return null; return null;
} }
@Override @Override
protected void done() { protected void done() {
// see if any exceptions were thrown // see if any exceptions were thrown
@ -677,53 +653,38 @@ public class GstVideoPanel extends MediaViewVideoPanel {
logger.log(Level.WARNING, "Error updating video progress: " + ex.getMessage()); //NON-NLS logger.log(Level.WARNING, "Error updating video progress: " + ex.getMessage()); //NON-NLS
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.infoLabel.updateErr", 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 } //end class progress worker
/* Thread that extracts and plays a file */ /* Thread that extracts and plays a file */
private class ExtractMedia extends SwingWorker<Object, Void> { private class ExtractMedia extends SwingWorker<Long, Void> {
private ProgressHandle progress; private ProgressHandle progress;
boolean success = false; private final AbstractFile sourceFile;
private AbstractFile sFile; private final java.io.File tempFile;
private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { ExtractMedia(AbstractFile sFile, java.io.File jFile) {
this.sFile = sFile; this.sourceFile = sFile;
this.jFile = jFile; this.tempFile = jFile;
}
public long getExtractedBytes() {
return extractedBytes;
} }
@Override @Override
protected Object doInBackground() throws Exception { protected Long doInBackground() throws Exception {
success = false; if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
progress = ProgressHandleFactory.createHandle( progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> ExtractMedia.this.cancel(true));
NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sFile.getName()),
new Cancellable() {
@Override
public boolean cancel() {
return ExtractMedia.this.cancel(true);
}
});
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
progress.start(); progress.start(100);
progress.switchToDeterminate(100);
try { try {
extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true); return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
return 0L;
} }
success = true; }
return null; return 0L;
} }
/* clean up or start the worker threads */ /* clean up or start the worker threads */
@ -738,7 +699,9 @@ public class GstVideoPanel extends MediaViewVideoPanel {
} catch (Exception ex) { } catch (Exception ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
} finally { } finally {
if (progress != null) {
progress.finish(); progress.finish();
}
if (!this.isCancelled()) { if (!this.isCancelled()) {
playMedia(); playMedia();
} }
@ -746,11 +709,11 @@ public class GstVideoPanel extends MediaViewVideoPanel {
} }
void playMedia() { void playMedia() {
if (jFile == null || !jFile.exists()) { if (tempFile == null || !tempFile.exists()) {
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr")); progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
return; return;
} }
ClockTime dur = null; ClockTime dur;
synchronized (playbinLock) { synchronized (playbinLock) {
// must play, then pause and get state to get duration. // must play, then pause and get state to get duration.
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
@ -766,7 +729,6 @@ public class GstVideoPanel extends MediaViewVideoPanel {
gstPlaybin2.getState(); gstPlaybin2.getState();
dur = gstPlaybin2.queryDuration(); dur = gstPlaybin2.queryDuration();
} }
duration = dur.toString();
durationMillis = dur.toMillis(); durationMillis = dur.toMillis();
// pick out the total hours, minutes, seconds // pick out the total hours, minutes, seconds
@ -777,9 +739,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
durationSeconds -= totalMinutes * 60; durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds; totalSeconds = (int) durationSeconds;
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(() -> {
@Override
public void run() {
progressSlider.setMaximum((int) durationMillis); progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0); progressSlider.setMinimum(0);
@ -792,18 +752,18 @@ public class GstVideoPanel extends MediaViewVideoPanel {
pauseButton.setText("||"); pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker(); videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute(); videoProgressWorker.execute();
}
}); });
} }
} }
@Override @Override
public String[] getExtensions() { public String[] getExtensions() {
return EXTENSIONS; return EXTENSIONS.clone();
} }
@Override @Override
public List<String> getMimeTypes() { public List<String> getMimeTypes() {
return MIMETYPES; return MIMETYPES;
} }
} }

View File

@ -54,7 +54,7 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
* Image viewer part of the Media View layered pane. Uses JavaFX to display the * Image viewer part of the Media View layered pane. Uses JavaFX to display the
* image. * image.
*/ */
public class MediaViewImagePanel extends JPanel { public class MediaViewImagePanel extends JPanel implements DataContentViewerMedia.MediaViewPanel {
private static final Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName()); private static final Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName());
@ -64,20 +64,23 @@ public class MediaViewImagePanel extends JPanel {
private ImageView fxImageView; private ImageView fxImageView;
private BorderPane borderpane; private BorderPane borderpane;
private final Label errorLabel = new Label("Could not load image file into media view."); private final Label errorLabel = new Label("Could not load file into media view.");
private final Label tooLargeLabel = new Label("Could not load image file into media view (too large)."); private final Label tooLargeLabel = new Label("Could not load file into media view (too large).");
private final Label noReaderLabel = new Label("Image reader not found for file.");
static {
ImageIO.scanForPlugins();
}
/** /**
* mime types we should be able to display. if the mimetype is unknown we * mime types we should be able to display. if the mimetype is unknown we
* will fall back on extension and jpg/png header * will fall back on extension and jpg/png header
*/ */
static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedMimeTypes(); static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedImageMimeTypes();
/** /**
* extensions we should be able to display * extensions we should be able to display
*/ */
static private final List<String> supportedExtensions = ImageUtils.getSupportedExtensions().stream() static private final List<String> supportedExtensions = ImageUtils.getSupportedImageExtensions().stream()
.map("."::concat) .map("."::concat)
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -157,7 +160,7 @@ public class MediaViewImagePanel extends JPanel {
BufferedImage bufferedImage = ImageIO.read(inputStream); BufferedImage bufferedImage = ImageIO.read(inputStream);
if (bufferedImage == null) { if (bufferedImage == null) {
LOGGER.log(Level.WARNING, "Image reader not found for file: {0}", file.getName()); //NON-NLS LOGGER.log(Level.WARNING, "Image reader not found for file: {0}", file.getName()); //NON-NLS
borderpane.setCenter(noReaderLabel); borderpane.setCenter(errorLabel);
} else { } else {
Image fxImage = SwingFXUtils.toFXImage(bufferedImage, null); Image fxImage = SwingFXUtils.toFXImage(bufferedImage, null);
if (fxImage.isError()) { if (fxImage.isError()) {
@ -191,10 +194,21 @@ public class MediaViewImagePanel extends JPanel {
/** /**
* @return supported mime types * @return supported mime types
*/ */
@Override
public List<String> getMimeTypes() { public List<String> getMimeTypes() {
return Collections.unmodifiableList(Lists.newArrayList(supportedMimes)); return Collections.unmodifiableList(Lists.newArrayList(supportedMimes));
} }
/**
* returns supported extensions (each starting with .)
*
* @return
*/
@Override
public List<String> getExtensionsList() {
return getExtensions();
}
/** /**
* returns supported extensions (each starting with .) * returns supported extensions (each starting with .)
* *
@ -204,6 +218,12 @@ public class MediaViewImagePanel extends JPanel {
return Collections.unmodifiableList(supportedExtensions); 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. * 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 * WARNING: Do NOT modify this code. The content of this method is always
@ -218,4 +238,5 @@ public class MediaViewImagePanel extends JPanel {
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -21,16 +21,20 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.Dimension; import java.awt.Dimension;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level; import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JPanel; import javax.swing.JPanel;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
/** /**
* Video viewer part of the Media View layered pane. * Video viewer part of the Media View layered pane.
* Uses different engines depending on platform. * 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<String> AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
@ -107,10 +111,30 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture
/** /**
* Return the extensions supported by this video panel. * Return the extensions supported by this video panel.
*
* @return
*/ */
abstract public String[] getExtensions(); abstract public String[] getExtensions();
/** /**
* Return the MimeTypes supported by this video panel. * Return the MimeTypes supported by this video panel.
*/ */
@Override
abstract public List<String> getMimeTypes(); abstract public List<String> getMimeTypes();
@Override
public List<String> 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;
}
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -43,7 +43,7 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
static final int IMAGES_PER_PAGE = 200; static final int IMAGES_PER_PAGE = 200;
private Node parent; private Node parent;
private final HashMap<Integer, List<Node>> pages = new HashMap<Integer, List<Node>>(); private final HashMap<Integer, List<Node>> pages = new HashMap<>();
private int totalImages = 0; private int totalImages = 0;
private int totalPages = 0; private int totalPages = 0;
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
@ -57,13 +57,9 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
this.parent = arg; this.parent = arg;
this.iconSize = iconSize; this.iconSize = iconSize;
//
} }
// @Override
// protected Node copyNode(Node arg0) {
// return new ThumbnailViewNode(arg0);
// }
@Override @Override
protected void addNotify() { protected void addNotify() {
super.addNotify(); super.addNotify();
@ -85,7 +81,7 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
//TODO when lazy loading of original nodes is fixed //TODO when lazy loading of original nodes is fixed
//we should be asking the datamodel for the children instead //we should be asking the datamodel for the children instead
//and not counting the children nodes (which might not be preloaded at this point) //and not counting the children nodes (which might not be preloaded at this point)
final List<Node> suppContent = new ArrayList<Node>(); final List<Node> suppContent = new ArrayList<>();
for (Node child : parent.getChildren().getNodes()) { for (Node child : parent.getChildren().getNodes()) {
if (isSupported(child)) { if (isSupported(child)) {
++totalImages; ++totalImages;
@ -122,8 +118,6 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
pageNums[i] = i + 1; pageNums[i] = i + 1;
} }
setKeys(pageNums); setKeys(pageNums);
} }
@Override @Override

View File

@ -19,9 +19,17 @@
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import java.awt.Image; import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.lang.ref.SoftReference; 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.FilterNode;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
@ -31,9 +39,13 @@ import org.sleuthkit.datamodel.Content;
*/ */
class ThumbnailViewNode extends FilterNode { class ThumbnailViewNode extends FilterNode {
static private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif"));
private SoftReference<Image> iconCache = null; private SoftReference<Image> iconCache = null;
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
//private final BufferedImage defaultIconBI;
private SwingWorker<Image, Object> swingWorker;
private Timer timer;
/** /**
* the constructor * the constructor
@ -60,24 +72,55 @@ class ThumbnailViewNode extends FilterNode {
icon = iconCache.get(); icon = iconCache.get();
} }
if (icon == null) { if (icon != null) {
Content content = this.getLookup().lookup(Content.class);
if (content != null) {
icon = ImageUtils.getThumbnail(content, iconSize);
} else {
icon = ImageUtils.getDefaultThumbnail();
}
iconCache = new SoftReference<>(icon);
}
return icon; 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<Image, Object>() {
final private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("generating thumbnail for video file " + content.getName());
@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;
}
} }
public void setIconSize(int iconSize) { public void setIconSize(int iconSize) {
this.iconSize = iconSize; this.iconSize = iconSize;
iconCache = null; iconCache = null;
swingWorker = null;
} }
} }

View File

@ -30,6 +30,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -45,6 +46,7 @@ import javax.annotation.Nullable;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.opencv.core.Core;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper; import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
@ -55,6 +57,7 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
*
* Utilities for working with Images and creating thumbnails. Reuses thumbnails * Utilities for working with Images and creating thumbnails. Reuses thumbnails
* by storing them in the case's cache directory. * by storing them in the case's cache directory.
*/ */
@ -69,46 +72,113 @@ public class ImageUtils {
public static final int ICON_SIZE_MEDIUM = 100; public static final int ICON_SIZE_MEDIUM = 100;
public static final int ICON_SIZE_LARGE = 200; 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<String> SUPPORTED_MIME_TYPES = new TreeSet<>();
private static final List<String> SUPPORTED_EXTENSIONS = new ArrayList<>();
private static final List<String> SUPPORTED_IMAGE_EXTENSIONS;
private static final List<String> 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<String> SUPPORTED_IMAGE_MIME_TYPES;
private static final List<String> 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 FileTypeDetector fileTypeDetector;
private static final List<String> SUPPORTED_EXTENSIONS;
private static final TreeSet<String> SUPPORTED_MIME_TYPES;
/** thread that saves generated thumbnails to disk in the background */ /** thread that saves generated thumbnails to disk in the background */
private static final Executor imageSaver private static final Executor imageSaver
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern("icon saver-%d").build()); .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<String> getSupportedImageExtensions() {
return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
}
public static List<String> getSupportedVideoExtensions() {
return SUPPORTED_VIDEO_EXTENSIONS;
}
public static SortedSet<String> getSupportedImageMimeTypes() {
return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
}
public static List<String> getSupportedVideoMimeTypes() {
return SUPPORTED_VIDEO_MIME_TYPES;
}
public static List<String> getSupportedExtensions() { public static List<String> getSupportedExtensions() {
return Collections.unmodifiableList(SUPPORTED_EXTENSIONS); return Collections.unmodifiableList(SUPPORTED_EXTENSIONS);
} }
@ -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 * @return
* *
*/ */
public static boolean thumbnailSupported(AbstractFile file) { public static boolean thumbnailSupported(Content content) {
if (file.getSize() == 0) {
if (content.getSize() == 0) {
return false; return false;
} }
if (!(content instanceof AbstractFile)) {
return false;
}
AbstractFile file = (AbstractFile) content;
try { try {
String mimeType = getFileTypeDetector().getFileType(file); String mimeType = getFileTypeDetector().getFileType(file);
@ -162,7 +237,7 @@ public class ImageUtils {
} }
} catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) { } catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) {
LOGGER.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", 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); AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(SUPPORTED_MIME_TYPES);
if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) { if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) {
return true; return true;
@ -170,7 +245,6 @@ public class ImageUtils {
return false; return false;
} }
} }
}
// if we have an extension, check it // if we have an extension, check it
final String extension = file.getNameExtension(); final String extension = file.getNameExtension();
@ -182,22 +256,6 @@ public class ImageUtils {
return isJpegFileHeader(file) || isPngFileHeader(file); 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 * returns a lazily instatiated FileTypeDetector
* *
@ -220,11 +278,13 @@ public class ImageUtils {
* @param content * @param content
* @param iconSize * @param iconSize
* *
*
* @return a thumbnail for the given image or a default one if there was a * @return a thumbnail for the given image or a default one if there was a
* problem making a thumbnail. * problem making a thumbnail.
* *
* @deprecated use {@link #getThumbnail(org.sleuthkit.datamodel.Content, int) * @deprecated use {@link #getThumbnail(org.sleuthkit.datamodel.Content, int)
* } instead. * } instead.
*
*/ */
@Nonnull @Nonnull
@Deprecated @Deprecated
@ -253,8 +313,8 @@ public class ImageUtils {
} else { } else {
return thumbnail; return thumbnail;
} }
} catch (IOException ex) { } catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error while reading image.", ex); //NON-NLS LOGGER.log(Level.WARNING, "Error while reading image: " + content.getName(), ex); //NON-NLS
return generateAndSaveThumbnail(content, iconSize, cacheFile); return generateAndSaveThumbnail(content, iconSize, cacheFile);
} }
} else { } else {
@ -274,6 +334,7 @@ public class ImageUtils {
* *
* @deprecated use {@link #getCachedThumbnailFile(org.sleuthkit.datamodel.Content, int) * @deprecated use {@link #getCachedThumbnailFile(org.sleuthkit.datamodel.Content, int)
* } instead. * } instead.
*
*/ */
@Nullable @Nullable
@Deprecated @Deprecated
@ -283,6 +344,7 @@ public class ImageUtils {
} }
/** /**
*
* Get a thumbnail of a specified size. Generates the image if it is * Get a thumbnail of a specified size. Generates the image if it is
* not already cached. * not already cached.
* *
@ -306,15 +368,30 @@ public class ImageUtils {
* *
* @return * @return
* *
* @deprecated this should never have been public. *
* @deprecated use {@link #getCachedThumbnailLocation(long) } instead
*/ */
@Deprecated @Deprecated
public static File getFile(long id) { public static File getFile(long id) {
return getCachedThumbnailLocation(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 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 * @param cacheFile Location to save thumbnail to
* *
* @return Generated icon or a default icon if a thumbnail could not be * @return Generated icon or null on error
* made.
*/ */
private static Image generateAndSaveThumbnail(Content content, int size, File cacheFile) { private static Image generateAndSaveThumbnail(Content content, int iconSize, File cacheFile) {
BufferedImage thumbnail = generateThumbnail(content, size); AbstractFile f = (AbstractFile) content;
if (Objects.nonNull(thumbnail)) { 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;
}
} else {
thumbnail = generateImageThumbnail(content, iconSize);
}
if (thumbnail == null) {
return DEFAULT_THUMBNAIL;
} else {
BufferedImage toSave = thumbnail;
imageSaver.execute(() -> { imageSaver.execute(() -> {
try { try {
Files.createParentDirs(cacheFile); Files.createParentDirs(cacheFile);
if (cacheFile.exists()) { if (cacheFile.exists()) {
cacheFile.delete(); cacheFile.delete();
} }
ImageIO.write(thumbnail, FORMAT, cacheFile); ImageIO.write(toSave, FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex1) { } catch (IllegalArgumentException | IOException ex1) {
LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex1); //NON-NLS LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex1); //NON-NLS
} }
}); });
return thumbnail;
} else {
return getDefaultThumbnail();
} }
} catch (NullPointerException ex) {
logger.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex); //NON-NLS
}
return thumbnail;
} }
/** /**
*
* Generate and return a scaled image * Generate and return a scaled image
* *
* @param content * @param content
@ -418,7 +513,7 @@ public class ImageUtils {
* there was a problem. * there was a problem.
*/ */
@Nullable @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));) { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(content));) {
BufferedImage bi = ImageIO.read(inputStream); BufferedImage bi = ImageIO.read(inputStream);
@ -436,10 +531,13 @@ public class ImageUtils {
} }
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
return null; return null;
} catch (Exception e) { } 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; return null;
} }
} }
} }

View File

@ -0,0 +1,127 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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);
}
}

View File

@ -23,14 +23,15 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.Future;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent; import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener; import java.util.prefs.PreferenceChangeListener;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.ContentVisitor;
@ -176,8 +177,8 @@ public final class ContentUtils {
* @return number of bytes extracted * @return number of bytes extracted
* @throws IOException if file could not be written * @throws IOException if file could not be written
*/ */
public static <T,V> long writeToFile(Content content, java.io.File outputFile, public static <T> long writeToFile(Content content, java.io.File outputFile,
ProgressHandle progress, SwingWorker<T,V> worker, boolean source) throws IOException { ProgressHandle progress, Future<T> worker, boolean source) throws IOException {
InputStream in = new ReadContentInputStream(content); InputStream in = new ReadContentInputStream(content);

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -382,7 +382,7 @@ public class IngestJobSettings {
if (isPythonModuleSettingsFile(moduleSettingsFilePath)) { if (isPythonModuleSettingsFile(moduleSettingsFilePath)) {
// compiled python modules have variable instance number as a part of their file name. // 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. // 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))) { try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(moduleSettingsFilePath))) {
out.writeObject(settings); out.writeObject(settings);

View File

@ -14,3 +14,7 @@ PhotoRecIngestModule.processTerminated=PhotoRec Carver ingest module was termina
PhotoRecIngestModule.moduleError=PhotoRec Carver Module Error PhotoRecIngestModule.moduleError=PhotoRec Carver Module Error
PhotoRecIngestModule.UnableToCarve=Unable to carve file: {0} PhotoRecIngestModule.UnableToCarve=Unable to carve file: {0}
PhotoRecIngestModule.NotEnoughDiskSpace=Not enough disk space to save unallocated file. Carving will be skipped. 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

View File

@ -32,7 +32,9 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level; import java.util.logging.Level;
import org.openide.modules.InstalledFileLocator; import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle; 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.FileIngestModule;
import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator; import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator;
import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestModule; import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
import org.sleuthkit.autopsy.ingest.IngestServices; 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 LOG_FILE = "run_log.txt"; //NON-NLS
private static final String TEMP_DIR_NAME = "temp"; // 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 Logger logger = Logger.getLogger(PhotoRecCarverFileIngestModule.class.getName());
private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private static final Map<Long, WorkingPaths> pathsByJob = new ConcurrentHashMap<>(); private static final Map<Long, WorkingPaths> pathsByJob = new ConcurrentHashMap<>();
private IngestJobContext context; private IngestJobContext context;
private Path rootOutputDirPath; private Path rootOutputDirPath;
private File executableFile; private File executableFile;
private IngestServices services; 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 * @inheritDoc
@ -85,6 +106,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
this.context = context; this.context = context;
this.services = IngestServices.getInstance(); this.services = IngestServices.getInstance();
this.jobId = this.context.getJobId();
// If the global unallocated space processing setting and the module // If the global unallocated space processing setting and the module
// process unallocated space only setting are not in sych, throw an // 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); Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE);
executableFile = locateExecutable(execName.toString()); executableFile = locateExecutable(execName.toString());
if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.context.getJobId()) == 1) { if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.jobId) == 1) {
try { try {
// The first instance creates an output subdirectory with a date and time stamp // 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 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); Files.createDirectory(tempDirPath);
// Save the directories for the current job. // Save the directories for the current job.
PhotoRecCarverFileIngestModule.pathsByJob.put(this.context.getJobId(), new WorkingPaths(outputDirPath, tempDirPath)); PhotoRecCarverFileIngestModule.pathsByJob.put(this.jobId, new WorkingPaths(outputDirPath, tempDirPath));
} } catch (SecurityException | IOException | UnsupportedOperationException ex) {
catch (SecurityException | IOException | UnsupportedOperationException ex) {
throw new IngestModule.IngestModuleException(NbBundle.getMessage(this.getClass(), "cannotCreateOutputDir.message", ex.getLocalizedMessage())); throw new IngestModule.IngestModuleException(NbBundle.getMessage(this.getClass(), "cannotCreateOutputDir.message", ex.getLocalizedMessage()));
} }
} }
@ -131,6 +152,9 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
return IngestModule.ProcessResult.OK; return IngestModule.ProcessResult.OK;
} }
// Safely get a reference to the totalsForIngestJobs object
IngestJobTotals totals = getTotalsForIngestJobs(jobId);
Path tempFilePath = null; Path tempFilePath = null;
try { try {
long id = getRootId(file); long id = getRootId(file);
@ -160,7 +184,8 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
} }
// Write the file to disk. // 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()); tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName());
ContentUtils.writeToFile(file, tempFilePath.toFile()); ContentUtils.writeToFile(file, tempFilePath.toFile());
@ -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 // 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); PhotoRecCarverOutputParser parser = new PhotoRecCarverOutputParser(outputDirPath);
List<LayoutFile> theList = parser.parse(newAuditFile, id, file); List<LayoutFile> carvedItems = parser.parse(newAuditFile, id, file);
if (theList != null) { // if there were any results from carving, add the unallocated carving event to the reports list. long calcdelta = (System.currentTimeMillis() - calcstart);
context.addFilesToJob(new ArrayList<>(theList)); totals.totalParsetime.addAndGet(calcdelta);
services.fireModuleContentEvent(new ModuleContentEvent(theList.get(0))); // fire an event to update the tree 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 logger.log(Level.SEVERE, "Error processing " + file.getName() + " with PhotoRec carver", ex); // NON-NLS
return IngestModule.ProcessResult.ERROR; return IngestModule.ProcessResult.ERROR;
} } finally {
finally {
if (null != tempFilePath && Files.exists(tempFilePath)) { if (null != tempFilePath && Files.exists(tempFilePath)) {
// Get rid of the unallocated space file. // Get rid of the unallocated space file.
tempFilePath.toFile().delete(); tempFilePath.toFile().delete();
@ -243,18 +271,47 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
} }
} }
private synchronized void postSummary() {
IngestJobTotals jobTotals = totalsForIngestJobs.remove(jobId);
StringBuilder detailsSb = new StringBuilder();
//details
detailsSb.append("<table border='0' cellpadding='4' width='280'>"); //NON-NLS
detailsSb.append("<tr><td>") //NON-NLS
.append(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.complete.numberOfCarved"))
.append("</td>"); //NON-NLS
detailsSb.append("<td>").append(jobTotals.totalItemsRecovered.get()).append("</td></tr>"); //NON-NLS
detailsSb.append("<tr><td>") //NON-NLS
.append(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.complete.totalWritetime"))
.append("</td><td>").append(jobTotals.totalWritetime.get()).append("</td></tr>\n"); //NON-NLS
detailsSb.append("<tr><td>") //NON-NLS
.append(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.complete.totalParsetime"))
.append("</td><td>").append(jobTotals.totalParsetime.get()).append("</td></tr>\n"); //NON-NLS
detailsSb.append("</table>"); //NON-NLS
services.postMessage(IngestMessage.createMessage(
IngestMessage.MessageType.INFO,
PhotoRecCarverIngestModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"PhotoRecIngestModule.complete.photoRecResults"),
detailsSb.toString()));
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@Override @Override
public void shutDown() { public void shutDown() {
if (this.context != null && refCounter.decrementAndGet(this.context.getJobId()) == 0) { if (this.context != null && refCounter.decrementAndGet(this.jobId) == 0) {
try { try {
// The last instance of this module for an ingest job cleans out // 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. // 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())); FileUtil.deleteDir(new File(paths.getTempDirPath().toString()));
postSummary();
} }
catch (SecurityException ex) { catch (SecurityException ex) {
logger.log(Level.SEVERE, "Error shutting down PhotoRec carver module", ex); // NON-NLS logger.log(Level.SEVERE, "Error shutting down PhotoRec carver module", ex); // NON-NLS

View File

@ -251,7 +251,7 @@ public interface TimeLineChart<X> extends TimeLineView {
final X end = getSpanEnd(); final X end = getSpanEnd();
Tooltip.uninstall(this, tooltip); Tooltip.uninstall(this, tooltip);
tooltip = new Tooltip( tooltip = new Tooltip(
NbBundle.getMessage(this.getClass(), "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start), NbBundle.getMessage(TimeLineChart.class, "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start),
formatSpan(end))); formatSpan(end)));
Tooltip.install(this, tooltip); Tooltip.install(this, tooltip);
} }

View File

@ -4,7 +4,7 @@
<configuration> <configuration>
<data xmlns="http://www.netbeans.org/ns/nb-module-project/3"> <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
<code-name-base>org.sleuthkit.autopsy.imagegallery</code-name-base> <code-name-base>org.sleuthkit.autopsy.imagegallery</code-name-base>
<standalone/> <suite-component/>
<module-dependencies> <module-dependencies>
<dependency> <dependency>
<code-name-base>org.netbeans.api.progress</code-name-base> <code-name-base>org.netbeans.api.progress</code-name-base>
@ -103,7 +103,7 @@
<compile-dependency/> <compile-dependency/>
<run-dependency> <run-dependency>
<release-version>10</release-version> <release-version>10</release-version>
<specification-version>10.0.11</specification-version> <specification-version>10.3</specification-version>
</run-dependency> </run-dependency>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -26,6 +26,7 @@ import static java.util.Objects.isNull;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -80,6 +81,10 @@ public enum FileTypeUtils {
*/ */
private static FileTypeDetector FILE_TYPE_DETECTOR; private static FileTypeDetector FILE_TYPE_DETECTOR;
private static final String IMAGE_GIF_MIME = "image/gif";
private static final TreeSet<String> GIF_MIME_SET = new TreeSet<>(Arrays.asList(IMAGE_GIF_MIME));
/** /**
* static initalizer block to initialize sets of extensions and mimetypes * static initalizer block to initialize sets of extensions and mimetypes
* to be supported * to be supported
@ -164,7 +169,7 @@ public enum FileTypeUtils {
* *
* @return true if this file is supported or false if not * @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(() -> { return hasDrawableMimeType(file).orElseGet(() -> {
final boolean contains = FileTypeUtils.supportedExtensions.contains(file.getNameExtension()); final boolean contains = FileTypeUtils.supportedExtensions.contains(file.getNameExtension());
final boolean jpegFileHeader = ImageUtils.isJpegFileHeader(file); 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 * does the given file have drawable/supported mime type
* *

View File

@ -751,9 +751,9 @@ public final class ImageGalleryController {
"' or name LIKE '%.") "' or name LIKE '%.")
+ "')"; + "')";
static private final String MIMETYPE_CLAUSE static private final String MIMETYPE_CLAUSE
= "blackboard_attributes.value_text LIKE " = "blackboard_attributes.value_text LIKE '"
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(), + 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 (" 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" + "SELECT tsk_files.obj_id from tsk_files , blackboard_artifacts, blackboard_attributes"

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.cache.CacheLoader;
import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.common.util.concurrent.UncheckedExecutionException;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@ -42,6 +43,7 @@ import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** Singleton to manage creation and access of icons. Keeps a cache in memory of /** Singleton to manage creation and access of icons. Keeps a cache in memory of
@ -54,7 +56,7 @@ public enum ThumbnailCache {
instance; 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()); private static final Logger LOGGER = Logger.getLogger(ThumbnailCache.class.getName());
@ -118,17 +120,23 @@ public enum ThumbnailCache {
*/ */
private Optional<Image> load(DrawableFile<?> file) { private Optional<Image> load(DrawableFile<?> file) {
BufferedImage thumbnail; if (FileTypeUtils.isGIF(file)) {
try { //directly read gif to preserve potential animation,
thumbnail = getCacheFile(file).map(new Function<File, BufferedImage>() { //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<File, BufferedImage>() {
@Override @Override
public BufferedImage apply(File cachFile) { public BufferedImage apply(File cachFile) {
if (cachFile.exists()) { if (cachFile.exists()) {
// If a thumbnail file is already saved locally, load it // If a thumbnail file is already saved locally, load it
try { try {
BufferedImage read = ImageIO.read(cachFile); BufferedImage read = ImageIO.read(cachFile);
if (read.getWidth() == MAX_ICON_SIZE) {
if (read.getWidth() < MAX_THUMBNAIL_SIZE) {
return read; return read;
} }
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
LOGGER.log(Level.WARNING, "Unable to parse cache file path.."); LOGGER.log(Level.WARNING, "Unable to parse cache file path..");
@ -139,17 +147,17 @@ public enum ThumbnailCache {
return null; return null;
} }
}).orElseGet(() -> { }).orElseGet(() -> {
return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_ICON_SIZE); return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE);
}); });
} catch (IllegalStateException e) { // } catch (IllegalStateException e) {
LOGGER.log(Level.WARNING, "can't load icon when no case is open"); // LOGGER.log(Level.WARNING, "can't load icon when no case is open");
return Optional.empty(); // return Optional.empty();
} // }
WritableImage jfxthumbnail; WritableImage jfxthumbnail;
if (thumbnail == ImageUtils.getDefaultThumbnail()) { 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 { } else {
jfxthumbnail = SwingFXUtils.toFXImage(thumbnail, null); jfxthumbnail = SwingFXUtils.toFXImage(thumbnail, null);
} }
@ -167,7 +175,8 @@ public enum ThumbnailCache {
*/ */
private static Optional<File> getCacheFile(DrawableFile<?> file) { private static Optional<File> getCacheFile(DrawableFile<?> file) {
try { try {
return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_ICON_SIZE)); return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_THUMBNAIL_SIZE));
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
LOGGER.log(Level.WARNING, "Failed to create cache file.{0}", e.getLocalizedMessage()); LOGGER.log(Level.WARNING, "Failed to create cache file.{0}", e.getLocalizedMessage());
return Optional.empty(); return Optional.empty();

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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 java.util.logging.Level;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.openide.util.Utilities; import org.openide.util.Utilities;
@ -91,19 +90,16 @@ public class AddDrawableTagAction extends AddTagAction {
.findAny(); .findAny();
if (duplicateTagName.isPresent()) { if (duplicateTagName.isPresent()) {
Platform.runLater(() -> { LOGGER.log(Level.INFO, "{0} already tagged as {1}. Skipping.", new Object[]{file.getName(), tagName.getDisplayName()});
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();
});
} else { } else {
LOGGER.log(Level.INFO, "Tagging {0} as {1}", new Object[]{file.getName(), tagName.getDisplayName()});
controller.getTagsManager().addContentTag(file, tagName, comment); controller.getTagsManager().addContentTag(file, tagName, comment);
} }
} catch (TskCoreException tskCoreException) { } catch (TskCoreException tskCoreException) {
LOGGER.log(Level.SEVERE, "Error tagging result", tskCoreException); LOGGER.log(Level.SEVERE, "Error tagging file", tskCoreException);
Platform.runLater(() -> { Platform.runLater(() -> {
new Alert(Alert.AlertType.ERROR, "Unable to file " + fileID + ".").show(); new Alert(Alert.AlertType.ERROR, "Unable to tag file " + fileID + ".").show();
}); });
} }
} }

View File

@ -18,10 +18,12 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.datamodel; package org.sleuthkit.autopsy.imagegallery.datamodel;
import java.lang.ref.SoftReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; 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.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; 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.Content;
import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream;
@ -90,6 +88,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
} }
} }
SoftReference<Image> imageRef;
private String drawablePath; private String drawablePath;
protected T file; protected T file;
@ -286,7 +286,12 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
} }
} }
public abstract Image getThumbnail();
public Image getThumbnail() {
return ThumbnailCache.getDefault().get(this);
}
public abstract Image getFullSizeImage();
public void setAnalyzed(Boolean analyzed) { public void setAnalyzed(Boolean analyzed) {
this.analyzed.set(analyzed); this.analyzed.set(analyzed);
@ -318,5 +323,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
} }
} }
public abstract boolean isDisplayable(); public boolean isDisplayableAsImage() {
Image thumbnail = getThumbnail();
return Objects.nonNull(thumbnail) && thumbnail.errorProperty().get() == false;
}
} }

View File

@ -22,13 +22,12 @@ import java.awt.image.BufferedImage;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.Objects;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils; import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.sleuthkit.autopsy.coreutils.Logger; 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.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream;
@ -45,22 +44,20 @@ public class ImageFile<T extends AbstractFile> extends DrawableFile<T> {
ImageIO.scanForPlugins(); ImageIO.scanForPlugins();
} }
private SoftReference<Image> imageRef;
ImageFile(T f, Boolean analyzed) { ImageFile(T f, Boolean analyzed) {
super(f, analyzed); super(f, analyzed);
} }
@Override
public Image getThumbnail() {
return ThumbnailCache.getDefault().get(this);
}
@Override
public Image getFullSizeImage() { public Image getFullSizeImage() {
Image image = null; Image image = (imageRef != null) ? imageRef.get() : null;
if (imageRef != null) { if (image == null || image.isError()) {
image = imageRef.get(); if (FileTypeUtils.isGIF(file)) {
//directly read gif to preserve potential animation,
image = new Image(new BufferedInputStream(new ReadContentInputStream(file)));
}
} }
if (image == null || image.isError()) { if (image == null || image.isError()) {
try (BufferedInputStream readContentInputStream = new BufferedInputStream(new ReadContentInputStream(this.getAbstractFile()))) { try (BufferedInputStream readContentInputStream = new BufferedInputStream(new ReadContentInputStream(this.getAbstractFile()))) {
@ -75,12 +72,6 @@ public class ImageFile<T extends AbstractFile> extends DrawableFile<T> {
return image; return image;
} }
@Override
public boolean isDisplayable() {
Image thumbnail = getThumbnail();
return Objects.nonNull(thumbnail) && thumbnail.errorProperty().get() == false;
}
@Override @Override
Double getWidth() { Double getWidth() {
final Image fullSizeImage = getFullSizeImage(); final Image fullSizeImage = getFullSizeImage();

View File

@ -19,17 +19,22 @@
package org.sleuthkit.autopsy.imagegallery.datamodel; package org.sleuthkit.autopsy.imagegallery.datamodel;
import com.google.common.io.Files; import com.google.common.io.Files;
import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Objects; import java.util.Objects;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.media.Media; import javafx.scene.media.Media;
import javafx.scene.media.MediaException; 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.Logger;
import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
@ -46,25 +51,36 @@ public class VideoFile<T extends AbstractFile> extends DrawableFile<T> {
} }
@Override @Override
public Image getThumbnail() { public Image getFullSizeImage() {
//TODO: implement video thumbnailing here? Image image = (null == imageRef) ? null : imageRef.get();
return getGenericVideoThumbnail();
if (image == null) {
final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024);
image = (bufferedImage == ImageUtils.getDefaultThumbnail()) ? null : SwingFXUtils.toFXImage(bufferedImage, null);
imageRef = new SoftReference<>(image);
} }
SoftReference<Media> mediaRef; return image;
}
private SoftReference<Media> mediaRef;
public Media getMedia() throws IOException, MediaException { public Media getMedia() throws IOException, MediaException {
Media media = null; Media media = (mediaRef != null) ? mediaRef.get() : null;
if (mediaRef != null) {
media = mediaRef.get();
}
if (media != null) { if (media != null) {
return media; return media;
} }
final File cacheFile = getCacheFile(this.getId()); final File cacheFile = VideoUtils.getTempVideoFile(this.getAbstractFile());
if (cacheFile.exists() == false) {
if (cacheFile.exists() == false || cacheFile.length() < getAbstractFile().getSize()) {
Files.createParentDirs(cacheFile); 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()); media = new Media(Paths.get(cacheFile.getAbsolutePath()).toUri().toString());
@ -73,12 +89,7 @@ public class VideoFile<T extends AbstractFile> extends DrawableFile<T> {
} }
private File getCacheFile(long id) { public boolean isDisplayableAsMedia() {
return Paths.get(Case.getCurrentCase().getCacheDirectory(), "videos", "" + id).toFile();
}
@Override
public boolean isDisplayable() {
try { try {
Media media = getMedia(); Media media = getMedia();
return Objects.nonNull(media) && Objects.isNull(media.getError()); return Objects.nonNull(media) && Objects.isNull(media.getError());

View File

@ -44,7 +44,6 @@ import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
@ -208,7 +207,6 @@ public class Toolbar extends ToolBar {
orderGroup.selectedToggleProperty().addListener(queryInvalidationListener); orderGroup.selectedToggleProperty().addListener(queryInvalidationListener);
ThumbnailCache.getDefault().iconSize.bind(sizeSlider.valueProperty());
} }

View File

@ -18,7 +18,6 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.gui; package org.sleuthkit.autopsy.imagegallery.gui;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.logging.Level; import java.util.logging.Level;
@ -27,7 +26,6 @@ import javafx.beans.InvalidationListener;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Slider; 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.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; 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_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"); 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; 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 @FXML
void initialize() { void initialize() {
assert controlButton != null : "fx:id=\"controlButton\" was not injected: check your FXML file 'MediaControl.fxml'."; 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.file = file;
this.mp = mp; this.mp = mp;
FXMLConstructor.construct(this, "MediaControl.fxml"); FXMLConstructor.construct(this, "MediaControl.fxml");

View File

@ -26,12 +26,10 @@ import javafx.scene.CacheHint;
import javafx.scene.control.Control; import javafx.scene.control.Control;
import javafx.scene.effect.DropShadow; import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.sleuthkit.autopsy.coreutils.Logger; 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.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableTileBase.globalSelectionModel; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableTileBase.globalSelectionModel;
import org.sleuthkit.datamodel.AbstractContent; import org.sleuthkit.datamodel.AbstractContent;
@ -50,17 +48,6 @@ public class DrawableTile extends DrawableTileBase {
private static final Logger LOGGER = Logger.getLogger(DrawableTile.class.getName()); 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 @FXML
@Override @Override
protected void initialize() { protected void initialize() {
@ -90,12 +77,6 @@ public class DrawableTile extends DrawableTileBase {
FXMLConstructor.construct(this, "DrawableTile.fxml"); FXMLConstructor.construct(this, "DrawableTile.fxml");
} }
@Override
@ThreadConfined(type = ThreadType.JFX)
protected void clearContent() {
imageView.setImage(null);
}
/** /**
* {@inheritDoc } * {@inheritDoc }
*/ */
@ -109,21 +90,13 @@ public class DrawableTile extends DrawableTileBase {
} }
@Override @Override
protected Runnable getContentUpdateRunnable() { CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
if (getFile().isPresent()) { return new ThumbnailLoaderTask(file);
Image image = getFile().get().getThumbnail();
return () -> {
imageView.setImage(image);
};
} else {
return () -> { //no-op
};
}
} }
@Override @Override
protected String getTextForLabel() { protected String getTextForLabel() {
return getFile().map(AbstractContent::getName).orElse(""); return getFile().map(AbstractContent::getName).orElse("");
} }
} }

View File

@ -54,8 +54,6 @@ import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider;
import org.sleuthkit.autopsy.coreutils.Logger; 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.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction; 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.actions.SwingMenuItemAdapter;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; 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.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
@ -106,7 +105,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@FXML @FXML
protected ImageView undisplayableImageView; protected ImageView undisplayableImageView;
** displays the icon representing follow up tag */ /** displays the icon representing follow up tag */
@FXML @FXML
private ImageView followUpImageView; private ImageView followUpImageView;
@ -120,8 +119,13 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@FXML @FXML
Label nameLabel; 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; final private GroupPane groupPane;
volatile private boolean registered = false; volatile private boolean registered = false;
protected DrawableTileBase(GroupPane groupPane) { protected DrawableTileBase(GroupPane groupPane) {
@ -235,13 +239,6 @@ public abstract class DrawableTileBase extends DrawableUIBase {
return groupPane; return groupPane;
} }
@ThreadConfined(type = ThreadType.UI)
protected abstract void clearContent();
protected abstract void disposeContent();
protected abstract Runnable getContentUpdateRunnable();
protected abstract String getTextForLabel(); protected abstract String getTextForLabel();
protected void initialize() { protected void initialize() {
@ -279,6 +276,8 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@Override @Override
synchronized protected void setFileHelper(final Long newFileID) { synchronized protected void setFileHelper(final Long newFileID) {
setFileIDOpt(Optional.ofNullable(newFileID)); setFileIDOpt(Optional.ofNullable(newFileID));
setFileOpt(Optional.empty());
disposeContent(); disposeContent();
if (getFileID().isPresent() == false || Case.isCaseOpen() == false) { if (getFileID().isPresent() == false || Case.isCaseOpen() == false) {
@ -287,23 +286,18 @@ public abstract class DrawableTileBase extends DrawableUIBase {
getController().getTagsManager().unregisterListener(this); getController().getTagsManager().unregisterListener(this);
registered = false; registered = false;
} }
setFileOpt(Optional.empty()); updateContent();
Platform.runLater(() -> {
clearContent();
});
} else { } else {
if (registered == false) { if (registered == false) {
getController().getCategoryManager().registerListener(this); getController().getCategoryManager().registerListener(this);
getController().getTagsManager().registerListener(this); getController().getTagsManager().registerListener(this);
registered = true; registered = true;
} }
setFileOpt(Optional.empty());
updateSelectionState(); updateSelectionState();
updateCategory(); updateCategory();
updateFollowUpIcon(); updateFollowUpIcon();
updateUI(); updateUI();
Platform.runLater(getContentUpdateRunnable()); updateContent();
} }
} }
@ -311,7 +305,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
getFile().ifPresent(file -> { getFile().ifPresent(file -> {
final boolean isVideo = file.isVideo(); final boolean isVideo = file.isVideo();
final boolean hasHashSetHits = hasHashHit(); final boolean hasHashSetHits = hasHashHit();
final boolean isUndisplayable = file.isDisplayable() == false; final boolean isUndisplayable = (isVideo ? ((VideoFile<?>) file).isDisplayableAsMedia() : file.isDisplayableAsImage()) == false;
final String text = getTextForLabel(); final String text = getTextForLabel();
Platform.runLater(() -> { Platform.runLater(() -> {
@ -387,4 +381,40 @@ public abstract class DrawableTileBase extends DrawableUIBase {
followUpToggle.setSelected(hasFollowUp); 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();
// }
// }
// }
} }

View File

@ -18,11 +18,22 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.lang.ref.SoftReference;
import java.util.Objects; import java.util.Objects;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level; 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.AnchorPane;
import javafx.scene.layout.BorderPane;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@ -33,11 +44,21 @@ import org.sleuthkit.datamodel.TskCoreException;
*/ */
abstract public class DrawableUIBase extends AnchorPane implements DrawableView { 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 final ImageGalleryController controller;
private Optional<DrawableFile<?>> fileOpt = Optional.empty(); private Optional<DrawableFile<?>> fileOpt = Optional.empty();
private Optional<Long> fileIDOpt = Optional.empty(); private Optional<Long> fileIDOpt = Optional.empty();
private Task<Image> imageTask;
private SoftReference<Image> imageCache;
private ProgressIndicator progressIndicator;
public DrawableUIBase(ImageGalleryController controller) { public DrawableUIBase(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
@ -86,10 +107,133 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
synchronized public void setFile(Long newFileID) { synchronized public void setFile(Long newFileID) {
if (getFileID().isPresent()) { if (getFileID().isPresent()) {
if (Objects.equals(newFileID, getFileID().get()) == false) { if (Objects.equals(newFileID, getFileID().get()) == false) {
setFileHelper(newFileID); if (Objects.nonNull(newFileID)) {
}
} else if (nonNull(newFileID)) {
setFileHelper(newFileID); setFileHelper(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<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file);
abstract class CachedLoaderTask<X, Y extends DrawableFile<?>> extends Task<X> {
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<Image, DrawableFile<?>> {
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();
}
}
} }

View File

@ -221,7 +221,7 @@ public class GroupPane extends BorderPane {
private final InvalidationListener filesSyncListener = (observable) -> { private final InvalidationListener filesSyncListener = (observable) -> {
final String header = getHeaderString(); final String header = getHeaderString();
final List<Long> fileIds = getGrouping().fileIds(); final List<Long> fileIds = getGroup().fileIds();
Platform.runLater(() -> { Platform.runLater(() -> {
slideShowToggle.setDisable(fileIds.isEmpty()); slideShowToggle.setDisable(fileIds.isEmpty());
gridView.getItems().setAll(fileIds); gridView.getItems().setAll(fileIds);
@ -247,8 +247,8 @@ public class GroupPane extends BorderPane {
} }
//assign last selected file or if none first file in group //assign last selected file or if none first file in group
if (slideShowFileID == null || getGrouping().fileIds().contains(slideShowFileID) == false) { if (slideShowFileID == null || getGroup().fileIds().contains(slideShowFileID) == false) {
slideShowPane.setFile(getGrouping().fileIds().get(0)); slideShowPane.setFile(getGroup().fileIds().get(0));
} else { } else {
slideShowPane.setFile(slideShowFileID); slideShowPane.setFile(slideShowFileID);
} }
@ -269,7 +269,7 @@ public class GroupPane extends BorderPane {
this.scrollToFileID(globalSelectionModel.lastSelectedProperty().get()); this.scrollToFileID(globalSelectionModel.lastSelectedProperty().get());
} }
public DrawableGroup getGrouping() { public DrawableGroup getGroup() {
return grouping.get(); return grouping.get();
} }
@ -278,7 +278,7 @@ public class GroupPane extends BorderPane {
menuItem.setOnAction(new EventHandler<ActionEvent>() { menuItem.setOnAction(new EventHandler<ActionEvent>() {
@Override @Override
public void handle(ActionEvent t) { public void handle(ActionEvent t) {
Set<Long> fileIdSet = new HashSet<>(getGrouping().fileIds()); Set<Long> fileIdSet = new HashSet<>(getGroup().fileIds());
new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet); new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet);
grpCatSplitMenu.setText(cat.getDisplayName()); grpCatSplitMenu.setText(cat.getDisplayName());
@ -293,7 +293,7 @@ public class GroupPane extends BorderPane {
menuItem.setOnAction(new EventHandler<ActionEvent>() { menuItem.setOnAction(new EventHandler<ActionEvent>() {
@Override @Override
public void handle(ActionEvent t) { public void handle(ActionEvent t) {
Set<Long> fileIdSet = new HashSet<>(getGrouping().fileIds()); Set<Long> fileIdSet = new HashSet<>(getGroup().fileIds());
new AddDrawableTagAction(controller).addTagsToFiles(tn, "", fileIdSet); new AddDrawableTagAction(controller).addTagsToFiles(tn, "", fileIdSet);
grpTagSplitMenu.setText(tn.getDisplayName()); grpTagSplitMenu.setText(tn.getDisplayName());
@ -304,14 +304,14 @@ public class GroupPane extends BorderPane {
} }
private void selectAllFiles() { private void selectAllFiles() {
globalSelectionModel.clearAndSelectAll(getGrouping().fileIds()); globalSelectionModel.clearAndSelectAll(getGroup().fileIds());
} }
/** create the string to display in the group header */ /** create the string to display in the group header */
protected String getHeaderString() { protected String getHeaderString() {
return isNull(getGrouping()) ? "" return isNull(getGroup()) ? ""
: StringUtils.defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- " : StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- "
+ getGrouping().getHashSetHitsCount() + " hash set hits / " + getGrouping().getSize() + " files"; + getGroup().getHashSetHitsCount() + " hash set hits / " + getGroup().getSize() + " files";
} }
ContextMenu getContextMenu() { ContextMenu getContextMenu() {
@ -580,11 +580,11 @@ public class GroupPane extends BorderPane {
* @param grouping the new grouping assigned to this group * @param grouping the new grouping assigned to this group
*/ */
void setViewState(GroupViewState viewState) { void setViewState(GroupViewState viewState) {
if (nonNull(getGrouping())) {
getGrouping().fileIds().removeListener(filesSyncListener);
}
if (isNull(viewState) || isNull(viewState.getGroup())) { if (isNull(viewState) || isNull(viewState.getGroup())) {
if (nonNull(getGroup())) {
getGroup().fileIds().removeListener(filesSyncListener);
}
this.grouping.set(null); this.grouping.set(null);
Platform.runLater(() -> { Platform.runLater(() -> {
@ -600,15 +600,18 @@ public class GroupPane extends BorderPane {
}); });
} else { } else {
if (this.grouping.get() != viewState.getGroup()) { if (getGroup() != viewState.getGroup()) {
if (nonNull(getGroup())) {
getGroup().fileIds().removeListener(filesSyncListener);
}
this.grouping.set(viewState.getGroup()); this.grouping.set(viewState.getGroup());
this.getGrouping().fileIds().addListener(filesSyncListener); getGroup().fileIds().addListener(filesSyncListener);
final String header = getHeaderString(); final String header = getHeaderString();
gridView.getItems().setAll(getGrouping().fileIds());
Platform.runLater(() -> { Platform.runLater(() -> {
gridView.getItems().setAll(getGroup().fileIds());
slideShowToggle.setDisable(gridView.getItems().isEmpty()); slideShowToggle.setDisable(gridView.getItems().isEmpty());
groupLabel.setText(header); groupLabel.setText(header);
resetScrollBar(); resetScrollBar();
@ -687,6 +690,7 @@ public class GroupPane extends BorderPane {
@Override @Override
protected void updateItem(Long item, boolean empty) { protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
tile.setFile(item); tile.setFile(item);
} }

View File

@ -13,7 +13,7 @@
<children> <children>
<VBox alignment="TOP_CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <VBox alignment="TOP_CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children> <children>
<BorderPane id="imageAnchor" fx:id="imageBorder" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" VBox.vgrow="NEVER"> <BorderPane id="imageAnchor" fx:id="imageBorder" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="220.0" minWidth="220.0" prefHeight="-1.0" prefWidth="-1.0" VBox.vgrow="NEVER">
<center> <center>
<ImageView fx:id="imageView" fitHeight="200.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" /> <ImageView fx:id="imageView" fitHeight="200.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</center> </center>

View File

@ -38,7 +38,6 @@ import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView; import javafx.scene.control.TableView;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.util.Pair; 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.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
@ -60,9 +60,6 @@ public class MetaDataPane extends DrawableUIBase {
private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName()); private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName());
@FXML
private ImageView imageView;
@FXML @FXML
private TableColumn<Pair<DrawableAttribute<?>, ? extends Object>, DrawableAttribute<?>> attributeColumn; private TableColumn<Pair<DrawableAttribute<?>, ? extends Object>, DrawableAttribute<?>> attributeColumn;
@ -72,9 +69,6 @@ public class MetaDataPane extends DrawableUIBase {
@FXML @FXML
private TableColumn<Pair<DrawableAttribute<?>, ? extends Object>, String> valueColumn; private TableColumn<Pair<DrawableAttribute<?>, ? extends Object>, String> valueColumn;
@FXML
private BorderPane imageBorder;
public MetaDataPane(ImageGalleryController controller) { public MetaDataPane(ImageGalleryController controller) {
super(controller); super(controller);
@ -155,22 +149,27 @@ public class MetaDataPane extends DrawableUIBase {
if (newFileID == null) { if (newFileID == null) {
Platform.runLater(() -> { Platform.runLater(() -> {
imageView.setImage(null); imageView.setImage(null);
imageBorder.setCenter(null);
tableView.getItems().clear(); tableView.getItems().clear();
getCategoryBorderRegion().setBorder(null); getCategoryBorderRegion().setBorder(null);
}); });
} else { } else {
disposeContent();
updateUI(); updateUI();
updateContent();
} }
} }
@Override
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
return new ThumbnailLoaderTask(file);
}
public void updateUI() { public void updateUI() {
getFile().ifPresent(file -> { getFile().ifPresent(file -> {
final Image icon = file.getThumbnail();
final ObservableList<Pair<DrawableAttribute<?>, ? extends Object>> attributesList = file.getAttributesList(); final ObservableList<Pair<DrawableAttribute<?>, ? extends Object>> attributesList = file.getAttributesList();
Platform.runLater(() -> { Platform.runLater(() -> {
imageView.setImage(icon);
tableView.getItems().clear(); tableView.getItems().clear();
tableView.getItems().setAll(attributesList); tableView.getItems().setAll(attributesList);
}); });

View File

@ -127,7 +127,10 @@
</BorderPane> </BorderPane>
</bottom> </bottom>
<center> <center>
<BorderPane fx:id="imageBorder" center="$imageView" maxHeight="-1.0" prefHeight="-1.0" prefWidth="-1.0" style="-fx-border-color: lightgray;&#10;-fx-border-width:10;&#10;-fx-border-radius:2;" BorderPane.alignment="CENTER" /> <BorderPane fx:id="imageBorder" center="$imageView" maxHeight="-1.0" prefHeight="-1.0" prefWidth="-1.0" style="-fx-border-color: lightgray;&#10;-fx-border-width:10;&#10;-fx-border-radius:2;" BorderPane.alignment="CENTER">
<center>
<ImageView fx:id="imageView" fitHeight="200.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" BorderPane.alignment="CENTER" />
</center></BorderPane>
</center> </center>
</BorderPane> </BorderPane>
</children> </children>

View File

@ -18,21 +18,27 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList; 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 java.util.logging.Level;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton; import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar; import javafx.scene.control.ToolBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import static javafx.scene.input.KeyCode.LEFT; import static javafx.scene.input.KeyCode.LEFT;
import static javafx.scene.input.KeyCode.RIGHT; 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.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; 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.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; 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.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; 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.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; 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 static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -100,31 +109,35 @@ public class SlideShowView extends DrawableTileBase {
private ToolBar toolBar; private ToolBar toolBar;
@FXML @FXML
private BorderPane footer; private BorderPane footer;
private Task<Node> mediaTask;
SlideShowView(GroupPane gp) { SlideShowView(GroupPane gp) {
super(gp); super(gp);
FXMLConstructor.construct(this, "SlideShow.fxml"); FXMLConstructor.construct(this, "SlideShowView.fxml");
} }
@FXML @FXML
@Override @Override
protected void initialize() { protected void initialize() {
super.initialize(); super.initialize();
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" 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 'SlideShow.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 'SlideShow.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 'SlideShow.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 'SlideShow.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 'SlideShow.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 'SlideShow.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 'SlideShow.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 'SlideShow.fxml'."; assert tagSplitButton != null : "fx:id=\"tagSplitButton\" was not injected: check your FXML file 'SlideShowView.fxml'.";
Platform.runLater(() -> { Platform.runLater(() -> {
HBox.setHgrow(spring, Priority.ALWAYS); HBox.setHgrow(spring, Priority.ALWAYS);
spring.setMinWidth(Region.USE_PREF_SIZE); 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) -> { tagSplitButton.setOnAction((ActionEvent t) -> {
try { try {
GuiUtils.createSelTagMenuItem(getController().getTagsManager().getFollowUpTagName(), tagSplitButton, getController()).getOnAction().handle(t); GuiUtils.createSelTagMenuItem(getController().getTagsManager().getFollowUpTagName(), tagSplitButton, getController()).getOnAction().handle(t);
@ -195,8 +208,8 @@ public class SlideShowView extends DrawableTileBase {
getGroupPane().grouping().addListener((Observable observable) -> { getGroupPane().grouping().addListener((Observable observable) -> {
syncButtonVisibility(); syncButtonVisibility();
if (getGroupPane().getGrouping() != null) { if (getGroupPane().getGroup() != null) {
getGroupPane().getGrouping().fileIds().addListener((Observable observable1) -> { getGroupPane().getGroup().fileIds().addListener((Observable observable1) -> {
syncButtonVisibility(); syncButtonVisibility();
}); });
} }
@ -206,7 +219,7 @@ public class SlideShowView extends DrawableTileBase {
@ThreadConfined(type = ThreadType.ANY) @ThreadConfined(type = ThreadType.ANY)
private void syncButtonVisibility() { private void syncButtonVisibility() {
try { try {
final boolean hasMultipleFiles = getGroupPane().getGrouping().fileIds().size() > 1; final boolean hasMultipleFiles = getGroupPane().getGroup().fileIds().size() > 1;
Platform.runLater(() -> { Platform.runLater(() -> {
rightButton.setVisible(hasMultipleFiles); rightButton.setVisible(hasMultipleFiles);
leftButton.setVisible(hasMultipleFiles); leftButton.setVisible(hasMultipleFiles);
@ -221,8 +234,8 @@ public class SlideShowView extends DrawableTileBase {
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
public void stopVideo() { public void stopVideo() {
if (imageBorder.getCenter() instanceof MediaControl) { if (imageBorder.getCenter() instanceof VideoPlayer) {
((MediaControl) imageBorder.getCenter()).stopVideo(); ((VideoPlayer) imageBorder.getCenter()).stopVideo();
} }
} }
@ -239,40 +252,40 @@ public class SlideShowView extends DrawableTileBase {
@Override @Override
protected void disposeContent() { protected void disposeContent() {
stopVideo(); stopVideo();
}
@Override super.disposeContent();
@ThreadConfined(type = ThreadType.UI) if (mediaTask != null) {
protected void clearContent() { mediaTask.cancel(true);
stopVideo();
imageBorder.setCenter(null);
} }
mediaTask = null;
mediaCache = null;
}
private SoftReference<Node> mediaCache;
/** {@inheritDoc } */ /** {@inheritDoc } */
@Override @Override
protected Runnable getContentUpdateRunnable() { Node getContentNode() {
if (getFile().isPresent() == false) {
return getFile().map(new Function<DrawableFile<?>, Runnable>() { mediaCache = null;
return super.getContentNode();
@Override
public Runnable apply(DrawableFile<?> file) {
if (file.isVideo()) {
return () -> {
imageBorder.setCenter(MediaControl.create((VideoFile<?>) file));
};
} else { } else {
ImageView imageView = new ImageView(((ImageFile<?>) file).getFullSizeImage()); DrawableFile<?> file = getFile().get();
imageView.setPreserveRatio(true); if (file.isVideo()) {
imageView.fitWidthProperty().bind(imageBorder.widthProperty().subtract(CAT_BORDER_WIDTH * 2)); Node mediaNode = (isNull(mediaCache)) ? null : mediaCache.get();
imageView.fitHeightProperty().bind(heightProperty().subtract(CAT_BORDER_WIDTH * 4).subtract(footer.heightProperty()).subtract(toolBar.heightProperty())); if (nonNull(mediaNode)) {
return () -> { return mediaNode;
imageBorder.setCenter(imageView); } else {
}; 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 } */ /** {@inheritDoc } */
@ -292,12 +305,12 @@ public class SlideShowView extends DrawableTileBase {
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
synchronized private void cycleSlideShowImage(int direction) { synchronized private void cycleSlideShowImage(int direction) {
stopVideo(); stopVideo();
final int groupSize = getGroupPane().getGrouping().fileIds().size(); final int groupSize = getGroupPane().getGroup().fileIds().size();
final Integer nextIndex = getFileID().map(fileID -> { 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; return (currentIndex + direction + groupSize) % groupSize;
}).orElse(0); }).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" * of y"
*/ */
private String getSupplementalText() { private String getSupplementalText() {
final ObservableList<Long> fileIds = getGroupPane().getGrouping().fileIds(); final ObservableList<Long> fileIds = getGroupPane().getGroup().fileIds();
return getFileID().map(fileID -> " ( " + (fileIds.indexOf(fileID) + 1) + " of " + fileIds.size() + " in group )") return getFileID().map(fileID -> " ( " + (fileIds.indexOf(fileID) + 1) + " of " + fileIds.size() + " in group )")
.orElse(""); .orElse("");
@ -365,4 +378,49 @@ public class SlideShowView extends DrawableTileBase {
}); });
} }
} }
@Override
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
return new ImageLoaderTask(file) {
@Override
Image load() {
return isCancelled() ? null : file.getFullSizeImage();
}
};
}
private class MediaLoadTask extends CachedLoaderTask<Node, VideoFile<?>> {
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.");
}
}
}
} }

View File

@ -141,7 +141,8 @@ KeywordSearch.cancelImportMsg=Cancel import
KeywordSearch.overwriteListPrompt=Keyword list <{0}> already exists locally, overwrite? KeywordSearch.overwriteListPrompt=Keyword list <{0}> already exists locally, overwrite?
KeywordSearch.importOwConflict=Import list conflict KeywordSearch.importOwConflict=Import list conflict
KeywordSearch.kwListFailImportMsg=Keyword list not imported 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 KeywordSearch.listImportFeatureTitle=Keyword List Import
KeywordSearchIngestModule.moduleName=Keyword Search KeywordSearchIngestModule.moduleName=Keyword Search
KeywordSearchIngestModule.moduleDescription=Performs file indexing and periodic search using keywords and regular expressions in lists. KeywordSearchIngestModule.moduleDescription=Performs file indexing and periodic search using keywords and regular expressions in lists.

View File

@ -210,10 +210,15 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa
private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed
JFileChooser chooser = new JFileChooser(); JFileChooser chooser = new JFileChooser();
final String[] EXTENSION = new String[]{"xml", "txt"}; //NON-NLS final String[] AUTOPSY_EXTENSIONS = new String[]{"xml"}; //NON-NLS
FileNameExtensionFilter filter = new FileNameExtensionFilter( final String[] ENCASE_EXTENSIONS = new String[]{"txt"}; //NON-NLS
NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.fileExtensionFilterLbl"), EXTENSION); FileNameExtensionFilter autopsyFilter = new FileNameExtensionFilter(
chooser.setFileFilter(filter); 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); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
String listName = null; String listName = null;

View File

@ -1,5 +1,5 @@
#Updated by build script #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 LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314 SPLASH_HEIGHT=314
SPLASH_WIDTH=538 SPLASH_WIDTH=538
@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18
SplashRunningTextColor=0x0 SplashRunningTextColor=0x0
SplashRunningTextFontSize=19 SplashRunningTextFontSize=19
currentVersion=Autopsy 3.1.2 currentVersion=Autopsy 3.1.3

View File

@ -1,5 +1,5 @@
#Updated by build script #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=Autopsy 3.1.3
CTL_MainWindow_Title_No_Project=Autopsy 3.1.2 CTL_MainWindow_Title_No_Project=Autopsy 3.1.3

View File

@ -1,4 +1,5 @@
branding.token=autopsy branding.token=autopsy
nbjdk.active=JDK_1.8u40x64
# Version of platform that is automatically downloaded # Version of platform that is automatically downloaded
# Note build.xml has similar definitions that should be kept in sync (manually) # Note build.xml has similar definitions that should be kept in sync (manually)
netbeans-plat-version=7.3.1 netbeans-plat-version=7.3.1
@ -13,111 +14,7 @@ cluster.path=\
${nbplatform.active.dir}/java:\ ${nbplatform.active.dir}/java:\
${nbplatform.active.dir}/platform ${nbplatform.active.dir}/platform
disabled.modules=\ disabled.modules=\
org.apache.tools.ant.module,\ org.netbeans.modules.jellytools.java,\
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.junit,\ org.netbeans.modules.junit,\
org.netbeans.modules.maven,\ org.netbeans.modules.whitelist
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

View File

@ -10,6 +10,7 @@ app.version=3.1.3
#build.type=RELEASE #build.type=RELEASE
build.type=DEVELOPMENT build.type=DEVELOPMENT
project.org.sleuthkit.autopsy.imagegallery=ImageGallery
update_versions=false update_versions=false
#custom JVM options #custom JVM options
#Note: can be higher on 64 bit systems, should be in sync with build.xml #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.testing}:\
${project.org.sleuthkit.autopsy.thunderbirdparser}:\ ${project.org.sleuthkit.autopsy.thunderbirdparser}:\
${project.org.sleuthkit.autopsy.core}:\ ${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.core=Core
project.org.sleuthkit.autopsy.corelibs=CoreLibs project.org.sleuthkit.autopsy.corelibs=CoreLibs
project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch

View File

@ -870,7 +870,9 @@ class TestResultsDiffer(object):
diff_path = os.path.splitext(os.path.basename(output_file))[0] diff_path = os.path.splitext(os.path.basename(output_file))[0]
diff_path += "-Diff.txt" diff_path += "-Diff.txt"
diff_file = codecs.open(diff_path, "wb", "utf_8") 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) subprocess.call(dffcmdlst, stdout = diff_file)
Errors.add_errors_out(diff_path) Errors.add_errors_out(diff_path)
@ -902,7 +904,8 @@ class TestResultsDiffer(object):
gold_report_path = test_data.get_html_report_path(DBType.GOLD) gold_report_path = test_data.get_html_report_path(DBType.GOLD)
output_report_path = test_data.get_html_report_path(DBType.OUTPUT) output_report_path = test_data.get_html_report_path(DBType.OUTPUT)
try: 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:' 'HTML Report Generated on \|Autopsy Report for case \|Case:\|Case Number:'
'\|Examiner:', gold_report_path, output_report_path])) '\|Examiner:', gold_report_path, output_report_path]))
print_report("", "REPORT COMPARISON", "The test reports matched the gold reports") print_report("", "REPORT COMPARISON", "The test reports matched the gold reports")

View File

@ -139,6 +139,7 @@ class TskDbDiff(object):
# If they are different, invoke 'diff' # If they are different, invoke 'diff'
diff_file = codecs.open(diff_path, "wb", "utf_8") 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] dffcmdlst = ["diff", gold_file, output_file]
subprocess.call(dffcmdlst, stdout = diff_file) subprocess.call(dffcmdlst, stdout = diff_file)