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>
<binary-origin>release/modules/ext/StixLib.jar</binary-origin>
</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>
<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 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.Component;
import java.awt.Dimension;
import java.util.Arrays;
import java.util.List;
import static java.util.Objects.nonNull;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
@ -32,7 +31,6 @@ import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
@ -48,16 +46,18 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
})
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
private static final Set<String> AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
private AbstractFile lastFile;
//UI
private final MediaViewVideoPanel videoPanel;
private final boolean videoPanelInited;
private final SortedSet<String> videoExtensions; // get them from the panel
private final SortedSet<String> videoMimes;
private final MediaViewImagePanel imagePanel;
private final boolean videoPanelInited;
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 VIDEO_VIEWER_LAYER = "VIDEO"; //NON-NLS
@ -71,14 +71,16 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
// get the right panel for our platform
videoPanel = MediaViewVideoPanel.createVideoPanel();
videoPanelInited = videoPanel.isInited();
videoExtensions = new TreeSet<>(Arrays.asList(videoPanel.getExtensions()));
videoExtensions = new TreeSet<>(videoPanel.getExtensionsList());
videoMimes = new TreeSet<>(videoPanel.getMimeTypes());
imagePanel = new MediaViewImagePanel();
imagePanelInited = imagePanel.isInited();
imageExtensions = new TreeSet<>(imagePanel.getExtensionsList());
imageMimes = new TreeSet<>(imagePanel.getMimeTypes());
customizeComponents();
logger.log(Level.INFO, "Created MediaView instance: " + this); //NON-NLS
logger.log(Level.INFO, "Created MediaView instance: {0}", this); //NON-NLS
}
private void customizeComponents() {
@ -125,12 +127,12 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
final Dimension dims = DataContentViewerMedia.this.getSize();
//logger.info("setting node on media viewer"); //NON-NLS
if (imagePanelInited && isImageSupported(file)) {
imagePanel.showImageFx(file, dims);
this.showVideoPanel(false);
} else if (videoPanelInited && isVideoSupported(file)) {
if (videoPanelInited && videoPanel.isSupported(file)) {
videoPanel.setupVideo(file, dims);
this.showVideoPanel(true);
} else if (imagePanelInited && imagePanel.isSupported(file)) {
imagePanel.showImageFx(file, dims);
this.showVideoPanel(false);
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while setting node", e); //NON-NLS
@ -186,24 +188,10 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
* @return True if a video file that can be displayed
*/
private boolean isVideoSupported(AbstractFile file) {
String extension = file.getNameExtension();
//TODO: is this what we want, to require both extension and mimetype support?
if (AUDIO_EXTENSIONS.contains("." + extension) || videoExtensions.contains("." + extension)) {
try {
String mimeType = new FileTypeDetector().getFileType(file);
if (nonNull(mimeType)) {
return videoMimes.contains(mimeType);
}
} catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) {
logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
if (!videoMimes.isEmpty() && file.isMimeType(videoMimes) == MimeMatchEnum.TRUE) {
return true;
}
}
if (null == file || file.getSize() == 0) {
return false;
}
return false;
return videoPanel.isSupported(file);
}
/**
@ -215,7 +203,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
*/
private boolean isImageSupported(AbstractFile file) {
return ImageUtils.thumbnailSupported(file);
if (null == file || file.getSize() == 0) {
return false;
}
return imagePanel.isSupported(file);
}
@Override
@ -256,4 +248,43 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
return 7;
}
}
interface MediaViewPanel {
/**
* @return supported mime types
*/
List<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,34 +16,42 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="thumbnailScrollPanel" pref="642" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="filePathLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="filePathLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="pageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pageNumLabel" min="-2" pref="95" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pagesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pagePrevButton" min="-2" pref="23" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="pageNextButton" min="-2" pref="23" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="goToPageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="goToPageField" min="-2" pref="54" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
<Component id="imagesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="imagesRangeLabel" min="-2" pref="91" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="thumbnailSizeComboBox" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="pageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pageNumLabel" min="-2" pref="95" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pagesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pagePrevButton" min="-2" pref="23" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="pageNextButton" min="-2" pref="23" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="goToPageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="goToPageField" min="-2" pref="54" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
<Component id="imagesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="imagesRangeLabel" min="-2" pref="91" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="thumbnailSizeComboBox" min="-2" 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="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -66,8 +74,8 @@
<Component id="thumbnailSizeComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="thumbnailScrollPanel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="iconView" pref="330" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="filePathLabel" min="-2" max="-2" attributes="0"/>
</Group>
@ -75,18 +83,6 @@
</DimensionLayout>
</Layout>
<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">
<Properties>
<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;"/>
</AuxValues>
</Component>
<Component class="org.openide.explorer.view.IconView" name="iconView">
</Component>
</SubComponents>
</Form>

View File

@ -27,9 +27,6 @@ import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle;
@ -37,7 +34,6 @@ import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.IconView;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
@ -45,9 +41,10 @@ import org.openide.nodes.NodeEvent;
import org.openide.nodes.NodeListener;
import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
@ -63,18 +60,18 @@ import org.sleuthkit.datamodel.TskCoreException;
// service provider when DataResultViewers can be made compatible with node
// multi-selection actions.
//@ServiceProvider(service = DataResultViewer.class)
final class DataResultViewerThumbnail extends AbstractDataResultViewer {
final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
//flag to keep track if images are being loaded
private int curPage;
private int totalPages;
private int curPageImages;
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
private final PageUpdater pageUpdater = new PageUpdater();
/**
* Creates a DataResultViewerThumbnail object that is compatible with node
* Creates a DataResultViewerThumbnail object that is compatible with node
* multiple selection actions.
*/
public DataResultViewerThumbnail(ExplorerManager explorerManager) {
@ -83,24 +80,24 @@ import org.sleuthkit.datamodel.TskCoreException;
}
/**
* Creates a DataResultViewerThumbnail object that is NOT compatible with
* Creates a DataResultViewerThumbnail object that is NOT compatible with
* node multiple selection actions.
*/
public DataResultViewerThumbnail() {
initialize();
}
}
private void initialize() {
initComponents();
((IconView) thumbnailScrollPanel).setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
curPage = -1;
totalPages = 0;
curPageImages = 0;
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -110,7 +107,6 @@ import org.sleuthkit.datamodel.TskCoreException;
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
thumbnailScrollPanel = new IconView();
pageLabel = new javax.swing.JLabel();
pagesLabel = new javax.swing.JLabel();
pagePrevButton = new javax.swing.JButton();
@ -122,32 +118,31 @@ import org.sleuthkit.datamodel.TskCoreException;
goToPageLabel = new javax.swing.JLabel();
goToPageField = new javax.swing.JTextField();
thumbnailSizeComboBox = new javax.swing.JComboBox<>();
thumbnailScrollPanel.setPreferredSize(new java.awt.Dimension(582, 348));
iconView = new org.openide.explorer.view.IconView();
pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageLabel.text")); // NOI18N
pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pagesLabel.text")); // NOI18N
pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N NON-NLS
pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pagePrevButton.text")); // NOI18N
pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N NON-NLS
pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N NON-NLS
pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pagePrevButtonActionPerformed(evt);
}
});
pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N NON-NLS
pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageNextButton.text")); // NOI18N
pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N NON-NLS
pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N NON-NLS
pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
pageNextButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pageNextButtonActionPerformed(evt);
@ -171,10 +166,7 @@ import org.sleuthkit.datamodel.TskCoreException;
}
});
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] {
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.smallThumbnails"),
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.mediumThumbnails"),
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.comboBox.largeThumbnails") }));
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "Small Thumbnails", "Medium Thumbnails", "Large Thumbnails" }));
thumbnailSizeComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
thumbnailSizeComboBoxActionPerformed(evt);
@ -185,32 +177,37 @@ import org.sleuthkit.datamodel.TskCoreException;
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 642, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(filePathLabel)
.addGroup(layout.createSequentialGroup()
.addComponent(pageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(filePathLabel)
.addGroup(layout.createSequentialGroup()
.addComponent(pageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0)
.addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(goToPageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(12, 12, 12)
.addComponent(imagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(goToPageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(12, 12, 12)
.addComponent(imagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 563, Short.MAX_VALUE)
.addGap(0, 0, 0)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -228,8 +225,8 @@ import org.sleuthkit.datamodel.TskCoreException;
.addComponent(goToPageLabel)
.addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addGap(0, 0, 0)
.addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 330, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(filePathLabel))
);
@ -248,28 +245,28 @@ import org.sleuthkit.datamodel.TskCoreException;
}//GEN-LAST:event_goToPageFieldActionPerformed
private void thumbnailSizeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_thumbnailSizeComboBoxActionPerformed
iconSize = ImageUtils.ICON_SIZE_MEDIUM; //default size
switch(thumbnailSizeComboBox.getSelectedIndex()) {
switch (thumbnailSizeComboBox.getSelectedIndex()) {
case 0:
iconSize = ImageUtils.ICON_SIZE_SMALL;
break;
case 2:
iconSize = ImageUtils.ICON_SIZE_LARGE;
break;
}
}
Node root = em.getRootContext();
for (Children c : Arrays.asList(root.getChildren()) ) {
((ThumbnailViewChildren)c).setIconSize(iconSize);
for (Children c : Arrays.asList(root.getChildren())) {
((ThumbnailViewChildren) c).setIconSize(iconSize);
}
for (Node page : root.getChildren().getNodes()) {
for (Node node : page.getChildren().getNodes()) {
((ThumbnailViewNode)node).setIconSize(iconSize);
((ThumbnailViewNode) node).setIconSize(iconSize);
}
}
}
// Temporarily set the explored context to the root, instead of a child node.
// This is a workaround hack to convince org.openide.explorer.ExplorerManager to
// update even though the new and old Node values are identical. This in turn
@ -283,6 +280,7 @@ import org.sleuthkit.datamodel.TskCoreException;
private javax.swing.JLabel filePathLabel;
private javax.swing.JTextField goToPageField;
private javax.swing.JLabel goToPageLabel;
private org.openide.explorer.view.IconView iconView;
private javax.swing.JLabel imagesLabel;
private javax.swing.JLabel imagesRangeLabel;
private javax.swing.JLabel pageLabel;
@ -290,7 +288,6 @@ import org.sleuthkit.datamodel.TskCoreException;
private javax.swing.JLabel pageNumLabel;
private javax.swing.JButton pagePrevButton;
private javax.swing.JLabel pagesLabel;
private javax.swing.JScrollPane thumbnailScrollPanel;
private javax.swing.JComboBox<String> thumbnailSizeComboBox;
// End of variables declaration//GEN-END:variables
@ -299,7 +296,14 @@ import org.sleuthkit.datamodel.TskCoreException;
if (selectedNode == null) {
return false;
}
return true;
Children ch = selectedNode.getChildren();
for (Node n : ch.getNodes()) {
if (ThumbnailViewChildren.isSupported(n)) {
return true;
}
}
return false;
}
@Override
@ -309,7 +313,7 @@ import org.sleuthkit.datamodel.TskCoreException;
try {
if (givenNode != null) {
ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, iconSize);
final Node root = new AbstractNode(childNode);
pageUpdater.setRoot(root);
root.addNodeListener(pageUpdater);
@ -318,14 +322,13 @@ import org.sleuthkit.datamodel.TskCoreException;
Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node
IconView iv = ((IconView) this.thumbnailScrollPanel);
iv.setBackground(Color.BLACK);
iconView.setBackground(Color.BLACK);
}
} finally {
this.setCursor(null);
}
}
@Override
public String getTitle() {
return NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.title");
@ -348,8 +351,8 @@ import org.sleuthkit.datamodel.TskCoreException;
@Override
public void clearComponent() {
this.thumbnailScrollPanel.removeAll();
this.thumbnailScrollPanel = null;
this.iconView.removeAll();
this.iconView = null;
super.clearComponent();
}
@ -369,28 +372,27 @@ import org.sleuthkit.datamodel.TskCoreException;
switchPage();
}
}
private void goToPage(String pageNumText) {
int newPage;
try {
newPage = Integer.parseInt(pageNumText);
}
catch (NumberFormatException e) {
} catch (NumberFormatException e) {
//ignore input
return;
}
if (newPage > totalPages || newPage < 1) {
JOptionPane.showMessageDialog(this,
NbBundle.getMessage(this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.msgDlg",
totalPages),
NbBundle.getMessage(this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.err"),
JOptionPane.WARNING_MESSAGE);
NbBundle.getMessage(this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.msgDlg",
totalPages),
NbBundle.getMessage(this.getClass(),
"DataResultViewerThumbnail.goToPageTextField.err"),
JOptionPane.WARNING_MESSAGE);
return;
}
curPage = newPage;
switchPage();
}
@ -418,7 +420,7 @@ import org.sleuthkit.datamodel.TskCoreException;
progress.start();
progress.switchToIndeterminate();
Node root = em.getRootContext();
Node pageNode = root.getChildren().getNodeAt(curPage - 1);
Node pageNode = root.getChildren().getNodeAt(curPage - 1);
em.setExploredContext(pageNode);
curPageImages = pageNode.getChildren().getNodesCount();
return null;
@ -428,16 +430,16 @@ import org.sleuthkit.datamodel.TskCoreException;
protected void done() {
progress.finish();
setCursor(null);
updateControls();
updateControls();
// see if any exceptions were thrown
try {
get();
} catch (InterruptedException | ExecutionException ex) {
NotifyDescriptor d =
new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg",
ex.getMessage()),
NotifyDescriptor.ERROR_MESSAGE);
NotifyDescriptor d
= new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg",
ex.getMessage()),
NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notify(d);
logger.log(Level.SEVERE, "Error making thumbnails: " + ex.getMessage()); //NON-NLS
} // catch and ignore if we were cancelled
@ -458,14 +460,14 @@ import org.sleuthkit.datamodel.TskCoreException;
} else {
pageNumLabel.setText(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal",
Integer.toString(curPage), Integer.toString(totalPages)));
Integer.toString(curPage), Integer.toString(totalPages)));
final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
imagesRangeLabel.setText(imagesFrom + "-" + imagesTo);
pageNextButton.setEnabled(!(curPage == totalPages));
pagePrevButton.setEnabled(!(curPage == 1));
goToPageField.setEnabled(totalPages>1);
goToPageField.setEnabled(totalPages > 1);
}
@ -534,7 +536,6 @@ import org.sleuthkit.datamodel.TskCoreException;
em.setExploredContext(pageNode);
}
updateControls();
}
@ -554,8 +555,9 @@ import org.sleuthkit.datamodel.TskCoreException;
public void nodeDestroyed(NodeEvent ne) {
}
}
private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
@ -566,26 +568,23 @@ import org.sleuthkit.datamodel.TskCoreException;
AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
if (af == null) {
filePathLabel.setText("");
}
else {
} else {
try {
String uPath = af.getUniquePath();
filePathLabel.setText(uPath);
filePathLabel.setToolTipText(uPath);
}
catch (TskCoreException e){
} catch (TskCoreException e) {
logger.log(Level.WARNING, "Could not get unique path for content: {0}", af.getName()); //NON-NLS
}
}
}
} else {
filePathLabel.setText("");
}
else {
filePathLabel.setText("");
}
}
finally {
} finally {
setCursor(null);
}
}
}
}
}
}
}

View File

@ -16,8 +16,35 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<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>
<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>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,7 +18,9 @@
*/
package org.sleuthkit.autopsy.corecomponents;
import com.google.common.io.Files;
import java.awt.Dimension;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
@ -26,11 +28,8 @@ import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.JFXPanel;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
@ -55,17 +54,15 @@ import static javafx.scene.media.MediaPlayer.Status.STOPPED;
import javafx.scene.media.MediaView;
import javafx.util.Duration;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
@ -78,58 +75,37 @@ import org.sleuthkit.datamodel.TskData;
@ServiceProvider(service = FrameCapture.class)
})
public class FXVideoPanel extends MediaViewVideoPanel {
// Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html
// for Javafx supported formats
private static final String[] EXTENSIONS = new String[]{".m4v", ".fxm", ".flv", ".m3u8", ".mp4", ".aif", ".aiff", ".mp3", "m4a", ".wav"}; //NON-NLS
private static final List<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;
// FX Components
private MediaPane mediaPane;
// Current media content representations
private AbstractFile currentFile;
// FX UI Components
private JFXPanel videoComponent;
/**
* Creates new form MediaViewVideoPanel
*/
public FXVideoPanel() {
fxInited = Installer.isJavaFxInited();
initComponents();
if (fxInited) {
setupFx();
Platform.runLater(() -> {
mediaPane = new MediaPane();
Scene fxScene = new Scene(mediaPane);
jFXPanel.setScene(fxScene);
});
}
}
@Deprecated
public JPanel getVideoPanel() {
return this;
}
private void setupFx() {
Platform.runLater(new Runnable() {
@Override
public void run() {
videoComponent = new JFXPanel();
mediaPane = new MediaPane();
Scene fxScene = new Scene(mediaPane);
videoComponent.setScene(fxScene);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
add(videoComponent);
}
});
}
});
}
@Override
void setupVideo(final AbstractFile file, final Dimension dims) {
if (file.equals(currentFile)) {
@ -148,50 +124,33 @@ public class FXVideoPanel extends MediaViewVideoPanel {
removeAll();
return;
}
mediaPane.setFit(dims);
String path = "";
try {
path = file.getUniquePath();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Cannot get unique path of video file"); //NON-NLS
logger.log(Level.SEVERE, "Cannot get unique path of video file", ex); //NON-NLS
}
mediaPane.setInfoLabelText(path);
mediaPane.setInfoLabelToolTipText(path);
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute();
final File tempFile = VideoUtils.getTempVideoFile(currentFile);
new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
mediaPane.setFit(dims);
}
@Override
void reset() {
Platform.runLater(new Runnable() {
@Override
public void run() {
if (mediaPane != null) {
mediaPane.reset();
}
Platform.runLater(() -> {
if (mediaPane != null) {
mediaPane.reset();
}
});
currentFile = null;
}
private java.io.File getJFile(AbstractFile file) {
// Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory();
String name = file.getName();
int extStart = name.lastIndexOf(".");
String ext = "";
if (extStart != -1) {
ext = name.substring(extStart, name.length()).toLowerCase();
}
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
java.io.File tempFile = new java.io.File(tempPath);
return tempFile;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -201,10 +160,34 @@ public class FXVideoPanel extends MediaViewVideoPanel {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jFXPanel = new javafx.embed.swing.JFXPanel();
setBackground(new java.awt.Color(0, 0, 0));
setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.LINE_AXIS));
javax.swing.GroupLayout jFXPanelLayout = new javax.swing.GroupLayout(jFXPanel);
jFXPanel.setLayout(jFXPanelLayout);
jFXPanelLayout.setHorizontalGroup(
jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
jFXPanelLayout.setVerticalGroup(
jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javafx.embed.swing.JFXPanel jFXPanel;
// End of variables declaration//GEN-END:variables
@Override
@ -212,134 +195,32 @@ public class FXVideoPanel extends MediaViewVideoPanel {
return fxInited;
}
/**
* Thread that extracts Media from a Sleuthkit file representation to a
* Java file representation that the Media Player can take as input.
*/
private class ExtractMedia extends SwingWorker<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 MediaPlayer mediaPlayer;
private MediaView mediaView;
private final MediaView mediaView;
/** The Duration of the media. * */
private Duration duration;
/** The container for the media controls. * */
private HBox mediaTools;
private final HBox mediaTools;
/** The container for the media video output. * */
private HBox mediaViewPane;
private final HBox mediaViewPane;
private VBox controlPanel;
private final VBox controlPanel;
private Slider progressSlider;
private final Slider progressSlider;
private Button pauseButton;
private final Button pauseButton;
private Button stopButton;
private final Button stopButton;
private Label progressLabel;
private final Label progressLabel;
private Label infoLabel;
private final Label infoLabel;
private int totalHours;
@ -347,22 +228,7 @@ public class FXVideoPanel extends MediaViewVideoPanel {
private int totalSeconds;
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
/** The EventHandler for MediaPlayer.onReady(). * */
private final ReadyListener READY_LISTENER = new ReadyListener();
/** The EventHandler for MediaPlayer.onEndOfMedia(). * */
private final EndOfMediaListener END_LISTENER = new EndOfMediaListener();
/** The EventHandler for the CurrentTime property of the MediaPlayer. * */
private final TimeListener TIME_LISTENER = new TimeListener();
/** The EventHandler for MediaPlayer.onPause and MediaPlayer.onStop. * */
private final NotPlayListener NOT_PLAY_LISTENER = new NotPlayListener();
/** The EventHandler for MediaPlayer.onPlay. * */
private final PlayListener PLAY_LISTENER = new PlayListener();
private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
private static final String PLAY_TEXT = "";
@ -409,22 +275,6 @@ public class FXVideoPanel extends MediaViewVideoPanel {
setProgressActionListeners();
}
/**
* Setup the MediaPane for media playback. Run on the JavaFx Thread.
*
*
* @param mediaUri the URI of the media
*/
public void prepareMedia(String mediaUri) {
try {
mediaPlayer = createMediaPlayer(mediaUri);
mediaView.setMediaPlayer(mediaPlayer);
} catch (MediaException ex) {
this.setProgressLabelText("");
this.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat"));
}
}
/**
* Reset this MediaPane.
*
@ -447,12 +297,9 @@ public class FXVideoPanel extends MediaViewVideoPanel {
* @param text
*/
public void setInfoLabelText(final String text) {
logger.log(Level.INFO, "Setting Info Label Text: " + text); //NON-NLS
Platform.runLater(new Runnable() {
@Override
public void run() {
infoLabel.setText(text);
}
logger.log(Level.INFO, "Setting Info Label Text: {0}", text); //NON-NLS
Platform.runLater(() -> {
infoLabel.setText(text);
});
}
@ -462,14 +309,11 @@ public class FXVideoPanel extends MediaViewVideoPanel {
* @param dims the current dimensions of the DataContentViewer
*/
public void setFit(final Dimension dims) {
Platform.runLater(new Runnable() {
@Override
public void run() {
setPrefSize(dims.getWidth(), dims.getHeight());
// Set the Video output to fit the size allocated for it. give an
// extra few px to ensure the info label will be shown
mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
}
Platform.runLater(() -> {
setPrefSize(dims.getWidth(), dims.getHeight());
// Set the Video output to fit the size allocated for it. give an
// extra few px to ensure the info label will be shown
mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
});
}
@ -498,7 +342,7 @@ public class FXVideoPanel extends MediaViewVideoPanel {
mediaPlayer.play();
break;
default:
logger.log(Level.INFO, "MediaPlayer in unexpected state: " + status.toString()); //NON-NLS
logger.log(Level.INFO, "MediaPlayer in unexpected state: {0}", status.toString()); //NON-NLS
// If the MediaPlayer is in an unexpected state, stop playback.
mediaPlayer.stop();
setInfoLabelText(NbBundle.getMessage(this.getClass(),
@ -508,27 +352,21 @@ public class FXVideoPanel extends MediaViewVideoPanel {
}
});
stopButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
if (mediaPlayer == null) {
return;
}
mediaPlayer.stop();
stopButton.setOnAction((ActionEvent e) -> {
if (mediaPlayer == null) {
return;
}
mediaPlayer.stop();
});
progressSlider.valueProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable o) {
if (mediaPlayer == null) {
return;
}
progressSlider.valueProperty().addListener((Observable o) -> {
if (mediaPlayer == null) {
return;
}
if (progressSlider.isValueChanging()) {
mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
}
if (progressSlider.isValueChanging()) {
mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
}
});
}
@ -557,13 +395,21 @@ public class FXVideoPanel extends MediaViewVideoPanel {
Media media = new Media(mediaUri);
MediaPlayer player = new MediaPlayer(media);
player.setOnReady(READY_LISTENER);
player.setOnPaused(NOT_PLAY_LISTENER);
player.setOnStopped(NOT_PLAY_LISTENER);
player.setOnPlaying(PLAY_LISTENER);
player.setOnEndOfMedia(END_LISTENER);
player.setOnReady(new ReadyListener());
final Runnable pauseListener = () -> {
pauseButton.setText(PLAY_TEXT);
};
player.setOnPaused(pauseListener);
player.setOnStopped(pauseListener);
player.setOnPlaying(() -> {
pauseButton.setText(PAUSE_TEXT);
});
player.setOnEndOfMedia(new EndOfMediaListener());
player.currentTimeProperty().addListener(TIME_LISTENER);
player.currentTimeProperty().addListener((observable, oldTime, newTime) -> {
updateSlider(newTime);
updateTime(newTime);
});
return player;
}
@ -614,31 +460,16 @@ public class FXVideoPanel extends MediaViewVideoPanel {
elapsedSeconds = (int) secondsElapsed;
String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds,
totalHours, totalMinutes, totalSeconds);
setProgressLabelText(durationStr);
}
/**
* Update the progress label to show the text.
*
* @param text
*/
private void setProgressLabelText(final String text) {
Platform.runLater(new Runnable() {
@Override
public void run() {
progressLabel.setText(text);
}
elapsedHours, elapsedMinutes, elapsedSeconds,
totalHours, totalMinutes, totalSeconds);
Platform.runLater(() -> {
progressLabel.setText(durationStr);
});
}
private void setInfoLabelToolTipText(final String text) {
Platform.runLater(new Runnable() {
@Override
public void run() {
infoLabel.setTooltip(new Tooltip(text));
}
Platform.runLater(() -> {
infoLabel.setTooltip(new Tooltip(text));
});
}
@ -654,7 +485,7 @@ public class FXVideoPanel extends MediaViewVideoPanel {
if (mediaPlayer == null) {
return;
}
duration = mediaPlayer.getMedia().getDuration();
long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis();
@ -692,38 +523,105 @@ public class FXVideoPanel extends MediaViewVideoPanel {
}
/**
* Responds to changes in the MediaPlayer currentTime property.
*
* Updates the progress slider and label with the current Time.
* Thread that extracts Media from a Sleuthkit file representation to a
* Java file representation that the Media Player can take as input.
*/
private class TimeListener implements ChangeListener<Duration> {
private class ExtractMedia extends Task<Long> {
@Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
updateSlider(newValue);
updateTime(newValue);
private ProgressHandle progress;
private final AbstractFile sourceFile;
private final java.io.File tempFile;
ExtractMedia(AbstractFile sFile, java.io.File jFile) {
this.sourceFile = sFile;
this.tempFile = jFile;
}
}
/**
* Triggered when MediaPlayer State changes to PAUSED or Stopped.
*/
private class NotPlayListener implements Runnable {
@Override
public void run() {
pauseButton.setText(PLAY_TEXT);
/**
* Get the URI of the media file.
*
* @return the URI of the media file.
*/
public String getMediaUri() {
return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
}
}
/**
* Triggered when MediaPlayer State changes to PLAYING.
*/
private class PlayListener implements Runnable {
@Override
public void run() {
pauseButton.setText(PAUSE_TEXT);
protected Long call() throws Exception {
if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
progress = ProgressHandleFactory.createHandle(
NbBundle.getMessage(this.getClass(),
"FXVideoPanel.progress.bufferingFile",
sourceFile.getName()
),
() -> ExtractMedia.this.cancel(true));
Platform.runLater(() -> {
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.buffering"));
});
progress.start(100);
try {
Files.createParentDirs(tempFile);
return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
return 0L;
} finally {
logger.log(Level.INFO, "Done buffering: {0}", tempFile.getName()); //NON-NLS
}
}
return 0L;
}
@Override
protected void failed() {
super.failed();
onDone();
}
@Override
protected void succeeded() {
super.succeeded();
onDone();
}
@Override
protected void cancelled() {
super.cancelled();
onDone();
}
private void onDone() {
progressLabel.setText("");
try {
super.get(); //block and get all exceptions thrown while doInBackground()
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingCancelled"));
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingInterrupted"));
} catch (Exception ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.errorWritingVideoToDisk"));
} finally {
if (null != progress) {
progress.finish();
}
if (!this.isCancelled()) {
logger.log(Level.INFO, "ExtractMedia is done: {0}", tempFile.getName()); //NON-NLS
try {
mediaPane.mediaPlayer = mediaPane.createMediaPlayer(getMediaUri());
mediaView.setMediaPlayer(mediaPane.mediaPlayer);
} catch (MediaException ex) {
progressLabel.setText("");
mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat"));
}
}
}
}
}
}
@ -739,114 +637,13 @@ public class FXVideoPanel extends MediaViewVideoPanel {
*/
@Override
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
//
// 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;
// }
//What is/was the point of this method /interface.
return null;
}
// private class FrameCapturer {
//
// private MediaPlayer mediaPlayer;
// private JFXPanel panel;
// private boolean isReady = false;
//
// FrameCapturer(java.io.File file) {
// initFx(file);
// }
//
// boolean isReady() {
// return isReady;
// }
//
// private void initFx(final java.io.File file) {
// Platform.runAndWait(new Runnable() {
// @Override
// public void run() {
// logger.log(Level.INFO, "In initFX.");
// // Create Media Player with no video output
// Media media = new Media(Paths.get(file.getAbsolutePath()).toUri().toString());
// mediaPlayer = new MediaPlayer(media);
// MediaView mediaView = new MediaView(mediaPlayer);
// mediaView.setStyle("-fx-background-color: black");
// Pane mediaViewPane = new Pane();
// mediaViewPane.getChildren().add(mediaView);
// Scene scene = new Scene(mediaViewPane);
// panel = new JFXPanel();
// panel.setScene(scene);
// isReady = true;
// }
// });
// }
//
// List<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
public String[] getExtensions() {
return EXTENSIONS;
return EXTENSIONS.clone();
}
@Override

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.IntBuffer;
import java.util.ArrayList;
@ -41,7 +42,6 @@ import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.gstreamer.ClockTime;
import org.gstreamer.Gst;
import org.gstreamer.GstException;
@ -52,26 +52,25 @@ import org.gstreamer.elements.RGBDataSink;
import org.gstreamer.swing.VideoComponent;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ServiceProviders(value = {
@ServiceProvider(service = FrameCapture.class)
})
public class GstVideoPanel extends MediaViewVideoPanel {
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
private static final List<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 Logger logger = Logger.getLogger(GstVideoPanel.class.getName());
private boolean gstInited;
private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
@ -87,7 +86,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
private AbstractFile currentFile;
private final Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
/**
* Creates new form MediaViewVideoPanel
*/
@ -129,30 +128,27 @@ public class GstVideoPanel extends MediaViewVideoPanel {
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
progressSlider.setValue(0);
progressSlider.addChangeListener(new ChangeListener() {
progressSlider.addChangeListener((ChangeEvent e) -> {
/**
* Should always try to synchronize any call to
* progressSlider.setValue() to avoid a different thread
* changing playbin while stateChanged() is processing
*/
@Override
public void stateChanged(ChangeEvent e) {
int time = progressSlider.getValue();
synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) {
State orig = gstPlaybin2.getState();
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.setState(orig);
int time = progressSlider.getValue();
synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) {
State orig = gstPlaybin2.getState();
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.setState(orig);
}
}
});
@ -195,7 +191,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
progressSlider.setEnabled(false);
return;
}
String path = "";
try {
path = file.getUniquePath();
@ -207,7 +203,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
pauseButton.setEnabled(true);
progressSlider.setEnabled(true);
java.io.File ioFile = getJFile(file);
java.io.File ioFile = VideoUtils.getTempVideoFile(file);
gstVideoComponent = new VideoComponent();
synchronized (playbinLock) {
@ -219,14 +215,13 @@ public class GstVideoPanel extends MediaViewVideoPanel {
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(gstVideoComponent);
videoPanel.setVisible(true);
gstPlaybin2.setInputFile(ioFile);
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
@ -239,11 +234,8 @@ public class GstVideoPanel extends MediaViewVideoPanel {
void reset() {
// reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressLabel.setText("");
}
SwingUtilities.invokeLater(() -> {
progressLabel.setText("");
});
if (!isInited()) {
@ -281,41 +273,27 @@ public class GstVideoPanel extends MediaViewVideoPanel {
currentFile = null;
}
private java.io.File getJFile(AbstractFile file) {
// Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory();
String name = file.getName();
int extStart = name.lastIndexOf(".");
String ext = "";
if (extStart != -1) {
ext = name.substring(extStart, name.length()).toLowerCase();
}
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
java.io.File tempFile = new java.io.File(tempPath);
return tempFile;
}
/**
* @param file a video file from which to capture frames
* @param file a video file from which to capture frames
* @param numFrames the number of frames to capture. These frames will be
* captured at successive intervals given by durationOfVideo/numFrames. If
* this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one
* frame will be captured and returned.
* captured at successive intervals given by durationOfVideo/numFrames. If
* this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one
* frame will be captured and returned.
*
* @return a List of VideoFrames representing the captured frames.
*/
@Override
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
List<VideoFrame> frames = new ArrayList<>();
Object lock = new Object();
FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock);
if (!isInited()) {
return frames;
}
// throw exception if this file is known to be problematic
if (badVideoFiles.contains(file.getName())) {
throw new Exception(
@ -374,7 +352,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
if (!playbin.seek(timeStamp, unit)) {
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); //NON-NLS
}
ret = playbin.play();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
@ -384,7 +362,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
}
// wait for FrameCaptureRGBListener to finish
synchronized(lock) {
synchronized (lock) {
try {
lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
} catch (InterruptedException e) {
@ -400,7 +378,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
throw new Exception(
NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemStopCaptFrame.msg"));
}
if (image == null) {
logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName()); //NON-NLS
badVideoFiles.add(file.getName());
@ -412,13 +390,13 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return frames;
}
private class FrameCaptureRGBListener implements RGBDataSink.Listener {
public FrameCaptureRGBListener(Object waiter) {
this.waiter = waiter;
}
private BufferedImage bi;
private final Object waiter;
@ -438,7 +416,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return image;
}
}
}
/**
@ -460,12 +438,12 @@ public class GstVideoPanel extends MediaViewVideoPanel {
javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
videoPanel.setLayout(videoPanelLayout);
videoPanelLayout.setHorizontalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
videoPanelLayout.setVerticalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 231, Short.MAX_VALUE)
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 231, Short.MAX_VALUE)
);
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(GstVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
@ -482,48 +460,48 @@ public class GstVideoPanel extends MediaViewVideoPanel {
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
controlPanel.setLayout(controlPanelLayout);
controlPanelLayout.setHorizontalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(infoLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())))
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(infoLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())))
);
controlPanelLayout.setVerticalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pauseButton)
.addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(infoLabel)
.addContainerGap())
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pauseButton)
.addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(infoLabel)
.addContainerGap())
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
);
}// </editor-fold>
@ -557,9 +535,10 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return;
}
} else if (state.equals(State.READY)) {
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute();
em.getExtractedBytes();
final File tempVideoFile = VideoUtils.getTempVideoFile(currentFile);
new ExtractMedia(currentFile, tempVideoFile).execute();
}
}
}//GEN-LAST:event_pauseButtonActionPerformed
@ -572,14 +551,13 @@ public class GstVideoPanel extends MediaViewVideoPanel {
private javax.swing.JSlider progressSlider;
private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables
private class VideoProgressWorker extends SwingWorker<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 final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50;
private boolean hadError = false;
private boolean isPlayBinReady() {
synchronized (playbinLock) {
@ -612,9 +590,9 @@ public class GstVideoPanel extends MediaViewVideoPanel {
/**
* @return true while millisElapsed is greater than END_TIME_MARGIN_MS
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
*/
private boolean hasNotEnded() {
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
@ -626,8 +604,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
// enable the slider
progressSlider.setEnabled(true);
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
ClockTime pos = null;
ClockTime pos;
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
synchronized (playbinLock) {
@ -637,11 +614,11 @@ public class GstVideoPanel extends MediaViewVideoPanel {
// pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000;
elapsedHours = (int) secondsElapsed / 3600;
int elapsedHours = (int) secondsElapsed / 3600;
secondsElapsed -= elapsedHours * 3600;
elapsedMinutes = (int) secondsElapsed / 60;
int elapsedMinutes = (int) secondsElapsed / 60;
secondsElapsed -= elapsedMinutes * 60;
elapsedSeconds = (int) secondsElapsed;
int elapsedSeconds = (int) secondsElapsed;
String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds,
@ -666,8 +643,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return null;
}
@Override
protected void done() {
// see if any exceptions were thrown
@ -676,54 +652,39 @@ public class GstVideoPanel extends MediaViewVideoPanel {
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.WARNING, "Error updating video progress: " + ex.getMessage()); //NON-NLS
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.infoLabel.updateErr",
ex.getMessage()));
ex.getMessage()));
} // catch and ignore if we were cancelled
catch (java.util.concurrent.CancellationException ex) {
}
// catch and ignore if we were cancelled
catch (java.util.concurrent.CancellationException ex ) { }
}
} //end class progress worker
/* Thread that extracts and plays a file */
private class ExtractMedia extends SwingWorker<Object, Void> {
private class ExtractMedia extends SwingWorker<Long, Void> {
private ProgressHandle progress;
boolean success = false;
private AbstractFile sFile;
private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes;
private final AbstractFile sourceFile;
private final java.io.File tempFile;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
this.sFile = sFile;
this.jFile = jFile;
}
public long getExtractedBytes() {
return extractedBytes;
ExtractMedia(AbstractFile sFile, java.io.File jFile) {
this.sourceFile = sFile;
this.tempFile = jFile;
}
@Override
protected Object doInBackground() throws Exception {
success = false;
progress = ProgressHandleFactory.createHandle(
NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sFile.getName()),
new Cancellable() {
@Override
public boolean cancel() {
return ExtractMedia.this.cancel(true);
protected Long doInBackground() throws Exception {
if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> ExtractMedia.this.cancel(true));
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
progress.start(100);
try {
return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
return 0L;
}
});
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
progress.start();
progress.switchToDeterminate(100);
try {
extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
}
success = true;
return null;
return 0L;
}
/* clean up or start the worker threads */
@ -738,7 +699,9 @@ public class GstVideoPanel extends MediaViewVideoPanel {
} catch (Exception ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
} finally {
progress.finish();
if (progress != null) {
progress.finish();
}
if (!this.isCancelled()) {
playMedia();
}
@ -746,11 +709,11 @@ public class GstVideoPanel extends MediaViewVideoPanel {
}
void playMedia() {
if (jFile == null || !jFile.exists()) {
if (tempFile == null || !tempFile.exists()) {
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
return;
}
ClockTime dur = null;
ClockTime dur;
synchronized (playbinLock) {
// must play, then pause and get state to get duration.
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
@ -766,7 +729,6 @@ public class GstVideoPanel extends MediaViewVideoPanel {
gstPlaybin2.getState();
dur = gstPlaybin2.queryDuration();
}
duration = dur.toString();
durationMillis = dur.toMillis();
// pick out the total hours, minutes, seconds
@ -777,33 +739,31 @@ public class GstVideoPanel extends MediaViewVideoPanel {
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0);
SwingUtilities.invokeLater(() -> {
progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0);
synchronized (playbinLock) {
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
synchronized (playbinLock) {
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
}
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
});
}
}
@Override
public String[] getExtensions() {
return EXTENSIONS;
return EXTENSIONS.clone();
}
@Override
public List<String> getMimeTypes() {
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.
*/
public class MediaViewImagePanel extends JPanel {
public class MediaViewImagePanel extends JPanel implements DataContentViewerMedia.MediaViewPanel {
private static final Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName());
@ -64,20 +64,23 @@ public class MediaViewImagePanel extends JPanel {
private ImageView fxImageView;
private BorderPane borderpane;
private final Label errorLabel = new Label("Could not load image file into media view.");
private final Label tooLargeLabel = new Label("Could not load image file into media view (too large).");
private final Label noReaderLabel = new Label("Image reader not found for file.");
private final Label errorLabel = new Label("Could not load file into media view.");
private final Label tooLargeLabel = new Label("Could not load file into media view (too large).");
static {
ImageIO.scanForPlugins();
}
/**
* mime types we should be able to display. if the mimetype is unknown we
* will fall back on extension and jpg/png header
*/
static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedMimeTypes();
static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedImageMimeTypes();
/**
* 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)
.collect(Collectors.toList());
@ -157,7 +160,7 @@ public class MediaViewImagePanel extends JPanel {
BufferedImage bufferedImage = ImageIO.read(inputStream);
if (bufferedImage == null) {
LOGGER.log(Level.WARNING, "Image reader not found for file: {0}", file.getName()); //NON-NLS
borderpane.setCenter(noReaderLabel);
borderpane.setCenter(errorLabel);
} else {
Image fxImage = SwingFXUtils.toFXImage(bufferedImage, null);
if (fxImage.isError()) {
@ -191,10 +194,21 @@ public class MediaViewImagePanel extends JPanel {
/**
* @return supported mime types
*/
@Override
public List<String> getMimeTypes() {
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 .)
*
@ -204,6 +218,12 @@ public class MediaViewImagePanel extends JPanel {
return Collections.unmodifiableList(supportedExtensions);
}
@Override
public boolean isSupported(AbstractFile file) {
return DataContentViewerMedia.MediaViewPanel.super.isSupported(file)
|| ImageUtils.hasImageFileHeader(file);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -218,4 +238,5 @@ public class MediaViewImagePanel extends JPanel {
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}

View File

@ -21,30 +21,34 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.Dimension;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JPanel;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Video viewer part of the Media View layered pane.
* Uses different engines depending on platform.
*/
public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture {
public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture, DataContentViewerMedia.MediaViewPanel {
private static final Set<String> AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
// 64 bit architectures
private static final String[] ARCH64 = new String[]{"amd64", "x86_64"}; //NON-NLS NON-NLS
// 32 bit architectures
private static final String[] ARCH32 = new String[]{"x86"}; //NON-NLS
/**
* Factory Method to create a MediaViewVideoPanel.
*
* Factory Method to create a MediaViewVideoPanel.
*
* Implementation is dependent on the architecture of the JVM.
*
*
* @return a MediaViewVideoPanel instance.
*/
public static MediaViewVideoPanel createVideoPanel() {
@ -56,10 +60,10 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture
return getGstImpl();
}
}
/**
* Is the JVM architecture 64 bit?
*
*
* @return
*/
private static boolean is64BitJVM() {
@ -69,34 +73,34 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture
/**
* Get a GStreamer video player implementation.
*
*
* @return a GstVideoPanel
*/
private static MediaViewVideoPanel getGstImpl() {
return new GstVideoPanel();
}
/**
/**
* Get a JavaFX video player implementation.
*
*
* @return a FXVideoPanel
*/
private static MediaViewVideoPanel getFXImpl() {
return new FXVideoPanel();
}
/**
* Has this MediaViewVideoPanel been initialized correctly?
*
* @return
*
* @return
*/
public abstract boolean isInited();
/**
* Prepare this MediaViewVideoPanel to accept a different media file.
*/
abstract void reset();
/**
* Initialize all the necessary vars to play a video/audio file.
*
@ -104,13 +108,33 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture
* @param dims dimension of the parent window
*/
abstract void setupVideo(final AbstractFile file, final Dimension dims);
/**
* Return the extensions supported by this video panel.
*
* @return
*/
abstract public String[] getExtensions();
/**
* Return the MimeTypes supported by this video panel.
*/
@Override
abstract public List<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
*
* Copyright 2011 Basis Technology Corp.
* Copyright 2011-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* 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;
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 totalPages = 0;
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
@ -57,13 +57,9 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
this.parent = arg;
this.iconSize = iconSize;
//
}
// @Override
// protected Node copyNode(Node arg0) {
// return new ThumbnailViewNode(arg0);
// }
@Override
protected void addNotify() {
super.addNotify();
@ -85,7 +81,7 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
//TODO when lazy loading of original nodes is fixed
//we should be asking the datamodel for the children instead
//and not counting the children nodes (which might not be preloaded at this point)
final List<Node> suppContent = new ArrayList<Node>();
final List<Node> suppContent = new ArrayList<>();
for (Node child : parent.getChildren().getNodes()) {
if (isSupported(child)) {
++totalImages;
@ -122,8 +118,6 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
pageNums[i] = i + 1;
}
setKeys(pageNums);
}
@Override

View File

@ -19,9 +19,17 @@
package org.sleuthkit.autopsy.corecomponents;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.lang.ref.SoftReference;
import java.util.concurrent.ExecutionException;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.datamodel.Content;
@ -31,9 +39,13 @@ import org.sleuthkit.datamodel.Content;
*/
class ThumbnailViewNode extends FilterNode {
static private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif"));
private SoftReference<Image> iconCache = null;
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
//private final BufferedImage defaultIconBI;
private SwingWorker<Image, Object> swingWorker;
private Timer timer;
/**
* the constructor
@ -55,29 +67,60 @@ class ThumbnailViewNode extends FilterNode {
@Override
public Image getIcon(int type) {
Image icon = null;
if (iconCache != null) {
icon = iconCache.get();
}
if (icon == null) {
Content content = this.getLookup().lookup(Content.class);
if (content != null) {
icon = ImageUtils.getThumbnail(content, iconSize);
} else {
icon = ImageUtils.getDefaultThumbnail();
if (icon != null) {
return icon;
} else {
final Content content = this.getLookup().lookup(Content.class);
if (content == null) {
return ImageUtils.getDefaultThumbnail();
}
if (swingWorker == null || swingWorker.isDone()) {
swingWorker = new SwingWorker<Image, Object>() {
final private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("generating thumbnail for video file " + content.getName());
iconCache = new SoftReference<>(icon);
@Override
protected Image doInBackground() throws Exception {
progressHandle.start();
return ImageUtils.getThumbnail(content, iconSize);
}
@Override
protected void done() {
super.done();
try {
iconCache = new SoftReference<>(super.get());
progressHandle.finish();
fireIconChange();
if (timer != null) {
timer.stop();
timer = null;
}
} catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
}
swingWorker = null;
}
};
swingWorker.execute();
}
if (timer == null) {
timer = new Timer(100, (ActionEvent e) -> {
fireIconChange();
});
timer.start();
}
return waitingIcon;
}
return icon;
}
public void setIconSize(int iconSize) {
this.iconSize = iconSize;
iconCache = null;
swingWorker = null;
}
}

View File

@ -30,6 +30,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -45,6 +46,7 @@ import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.opencv.core.Core;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
@ -55,6 +57,7 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
* Utilities for working with Images and creating thumbnails. Reuses thumbnails
* by storing them in the case's cache directory.
*/
@ -69,44 +72,111 @@ public class ImageUtils {
public static final int ICON_SIZE_MEDIUM = 100;
public static final int ICON_SIZE_LARGE = 200;
private static final Image DEFAULT_THUMBNAIL;
private static final Logger logger = LOGGER;
private static final BufferedImage DEFAULT_THUMBNAIL;
private static final TreeSet<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 final List<String> SUPPORTED_EXTENSIONS;
private static final TreeSet<String> SUPPORTED_MIME_TYPES;
/** thread that saves generated thumbnails to disk in the background */
private static final Executor imageSaver
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern("icon saver-%d").build());
static {
ImageIO.scanForPlugins();
BufferedImage defaultIcon = null;
try {
defaultIcon = ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/file-icon.png"));//NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Failed to read default thumbnail.");
}
DEFAULT_THUMBNAIL = defaultIcon;
SUPPORTED_EXTENSIONS = Arrays.asList(ImageIO.getReaderFileSuffixes());
SUPPORTED_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
/* special cases and variants that we support, but don't get registered
* with ImageIO automatically */
SUPPORTED_MIME_TYPES.addAll(Arrays.asList(
"image/x-rgb",
"image/x-ms-bmp",
"application/x-123"));
//this is rarely usefull
SUPPORTED_MIME_TYPES.removeIf("application/octet-stream"::equals);
private ImageUtils() {
}
private ImageUtils() {
public static List<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() {
@ -142,17 +212,22 @@ public class ImageUtils {
}
/**
* Can a thumbnail be generated for the file?
* Can a thumbnail be generated for the content?
*
* @param file
* @param content
*
* @return
*
*/
public static boolean thumbnailSupported(AbstractFile file) {
if (file.getSize() == 0) {
public static boolean thumbnailSupported(Content content) {
if (content.getSize() == 0) {
return false;
}
if (!(content instanceof AbstractFile)) {
return false;
}
AbstractFile file = (AbstractFile) content;
try {
String mimeType = getFileTypeDetector().getFileType(file);
@ -162,13 +237,12 @@ public class ImageUtils {
}
} catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) {
LOGGER.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
if (!SUPPORTED_MIME_TYPES.isEmpty()) {
AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(SUPPORTED_MIME_TYPES);
if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) {
return true;
} else if (mimeMatch == AbstractFile.MimeMatchEnum.FALSE) {
return false;
}
AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(SUPPORTED_MIME_TYPES);
if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) {
return true;
} else if (mimeMatch == AbstractFile.MimeMatchEnum.FALSE) {
return false;
}
}
@ -182,22 +256,6 @@ public class ImageUtils {
return isJpegFileHeader(file) || isPngFileHeader(file);
}
/**
* Can a thumbnail be generated for the content?
*
* @param content
*
* @return
*
* @deprecated use {@link #thumbnailSupported(org.sleuthkit.datamodel.AbstractFile) instead.
*/
@Deprecated
public static boolean thumbnailSupported(Content content) {
return (content instanceof AbstractFile)
? thumbnailSupported((AbstractFile) content)
: false;
}
/**
* returns a lazily instatiated FileTypeDetector
*
@ -220,11 +278,13 @@ public class ImageUtils {
* @param content
* @param iconSize
*
*
* @return a thumbnail for the given image or a default one if there was a
* problem making a thumbnail.
*
* @deprecated use {@link #getThumbnail(org.sleuthkit.datamodel.Content, int)
* } instead.
*
*/
@Nonnull
@Deprecated
@ -253,8 +313,8 @@ public class ImageUtils {
} else {
return thumbnail;
}
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Error while reading image.", ex); //NON-NLS
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error while reading image: " + content.getName(), ex); //NON-NLS
return generateAndSaveThumbnail(content, iconSize, cacheFile);
}
} else {
@ -274,6 +334,7 @@ public class ImageUtils {
*
* @deprecated use {@link #getCachedThumbnailFile(org.sleuthkit.datamodel.Content, int)
* } instead.
*
*/
@Nullable
@Deprecated
@ -283,6 +344,7 @@ public class ImageUtils {
}
/**
*
* Get a thumbnail of a specified size. Generates the image if it is
* not already cached.
*
@ -306,15 +368,30 @@ public class ImageUtils {
*
* @return
*
* @deprecated this should never have been public.
*
* @deprecated use {@link #getCachedThumbnailLocation(long) } instead
*/
@Deprecated
public static File getFile(long id) {
return getCachedThumbnailLocation(id);
}
private static File getCachedThumbnailLocation(long id) {
return Paths.get(Case.getCurrentCase().getCacheDirectory(), "thumbnails", id + ".png").toFile();
/**
* Get a file object for where the cached icon should exist. The returned
* file may not exist.
*
* @param fileID
*
* @return
*
*/
private static File getCachedThumbnailLocation(long fileID) {
return Paths.get(Case.getCurrentCase().getCacheDirectory(), "thumbnails", fileID + ".png").toFile();
}
public static boolean hasImageFileHeader(AbstractFile file) {
return isJpegFileHeader(file) || isPngFileHeader(file);
}
/**
@ -379,36 +456,54 @@ public class ImageUtils {
}
/**
* Generate a thumbnail and save it to specified location.
* Generate an icon and save it to specified location.
*
* @param content File to generate icon for
* @param size the size of thumbnail to generate in pixels
* @param iconSize
* @param cacheFile Location to save thumbnail to
*
* @return Generated icon or a default icon if a thumbnail could not be
* made.
* @return Generated icon or null on error
*/
private static Image generateAndSaveThumbnail(Content content, int size, File cacheFile) {
BufferedImage thumbnail = generateThumbnail(content, size);
if (Objects.nonNull(thumbnail)) {
imageSaver.execute(() -> {
try {
Files.createParentDirs(cacheFile);
if (cacheFile.exists()) {
cacheFile.delete();
}
ImageIO.write(thumbnail, FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex1) {
LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex1); //NON-NLS
private static Image generateAndSaveThumbnail(Content content, int iconSize, File cacheFile) {
AbstractFile f = (AbstractFile) content;
final String extension = f.getNameExtension();
BufferedImage thumbnail = null;
try {
if (SUPPORTED_VIDEO_EXTENSIONS.contains(extension)) {
if (openCVLoaded) {
thumbnail = VideoUtils.generateVideoThumbnail((AbstractFile) content, iconSize);
} else {
return DEFAULT_THUMBNAIL;
}
});
return thumbnail;
} else {
return getDefaultThumbnail();
} else {
thumbnail = generateImageThumbnail(content, iconSize);
}
if (thumbnail == null) {
return DEFAULT_THUMBNAIL;
} else {
BufferedImage toSave = thumbnail;
imageSaver.execute(() -> {
try {
Files.createParentDirs(cacheFile);
if (cacheFile.exists()) {
cacheFile.delete();
}
ImageIO.write(toSave, FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex1) {
LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex1); //NON-NLS
}
});
}
} catch (NullPointerException ex) {
logger.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex); //NON-NLS
}
return thumbnail;
}
/**
*
* Generate and return a scaled image
*
* @param content
@ -418,7 +513,7 @@ public class ImageUtils {
* there was a problem.
*/
@Nullable
private static BufferedImage generateThumbnail(Content content, int iconSize) {
private static BufferedImage generateImageThumbnail(Content content, int iconSize) {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(content));) {
BufferedImage bi = ImageIO.read(inputStream);
@ -436,10 +531,13 @@ public class ImageUtils {
}
} catch (OutOfMemoryError e) {
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
return null;
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not scale image: " + content.getName(), e); //NON-NLS
LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS
return null;
}
}
}

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.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
@ -176,8 +177,8 @@ public final class ContentUtils {
* @return number of bytes extracted
* @throws IOException if file could not be written
*/
public static <T,V> long writeToFile(Content content, java.io.File outputFile,
ProgressHandle progress, SwingWorker<T,V> worker, boolean source) throws IOException {
public static <T> long writeToFile(Content content, java.io.File outputFile,
ProgressHandle progress, Future<T> worker, boolean source) throws IOException {
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)) {
// compiled python modules have variable instance number as a part of their file name.
// This block of code gets rid of that variable instance number and helps maitains constant module name over multiple runs.
moduleSettingsFilePath.replaceAll("[$][\\d]+.settings$", "\\$.settings");
moduleSettingsFilePath = moduleSettingsFilePath.replaceAll("[$][\\d]+.settings$", "\\$.settings");
}
try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(moduleSettingsFilePath))) {
out.writeObject(settings);

View File

@ -13,4 +13,8 @@ cannotCreateOutputDir.message=Unable to create output directory: {0}
PhotoRecIngestModule.processTerminated=PhotoRec Carver ingest module was terminated due to exceeding max allowable run time when scanning
PhotoRecIngestModule.moduleError=PhotoRec Carver Module Error
PhotoRecIngestModule.UnableToCarve=Unable to carve file: {0}
PhotoRecIngestModule.NotEnoughDiskSpace=Not enough disk space to save unallocated file. Carving will be skipped.
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.List;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle;
@ -46,6 +48,7 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.FileIngestModule;
import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
import org.sleuthkit.autopsy.ingest.IngestServices;
@ -71,12 +74,30 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
private static final String LOG_FILE = "run_log.txt"; //NON-NLS
private static final String TEMP_DIR_NAME = "temp"; // NON-NLS
private static final Logger logger = Logger.getLogger(PhotoRecCarverFileIngestModule.class.getName());
private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private static final Map<Long, WorkingPaths> pathsByJob = new ConcurrentHashMap<>();
private IngestJobContext context;
private Path rootOutputDirPath;
private File executableFile;
private IngestServices services;
private long jobId;
private static class IngestJobTotals {
private AtomicLong totalItemsRecovered = new AtomicLong(0);
private AtomicLong totalWritetime = new AtomicLong(0);
private AtomicLong totalParsetime = new AtomicLong(0);
}
private static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId) {
IngestJobTotals totals = totalsForIngestJobs.get(ingestJobId);
if (totals == null) {
totals = new PhotoRecCarverFileIngestModule.IngestJobTotals();
totalsForIngestJobs.put(ingestJobId, totals);
}
return totals;
}
/**
* @inheritDoc
@ -85,6 +106,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
this.context = context;
this.services = IngestServices.getInstance();
this.jobId = this.context.getJobId();
// If the global unallocated space processing setting and the module
// process unallocated space only setting are not in sych, throw an
@ -99,7 +121,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE);
executableFile = locateExecutable(execName.toString());
if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.context.getJobId()) == 1) {
if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.jobId) == 1) {
try {
// The first instance creates an output subdirectory with a date and time stamp
DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS"); // NON-NLS
@ -113,9 +135,8 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
Files.createDirectory(tempDirPath);
// Save the directories for the current job.
PhotoRecCarverFileIngestModule.pathsByJob.put(this.context.getJobId(), new WorkingPaths(outputDirPath, tempDirPath));
}
catch (SecurityException | IOException | UnsupportedOperationException ex) {
PhotoRecCarverFileIngestModule.pathsByJob.put(this.jobId, new WorkingPaths(outputDirPath, tempDirPath));
} catch (SecurityException | IOException | UnsupportedOperationException ex) {
throw new IngestModule.IngestModuleException(NbBundle.getMessage(this.getClass(), "cannotCreateOutputDir.message", ex.getLocalizedMessage()));
}
}
@ -130,6 +151,9 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
if (file.getType() != TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
return IngestModule.ProcessResult.OK;
}
// Safely get a reference to the totalsForIngestJobs object
IngestJobTotals totals = getTotalsForIngestJobs(jobId);
Path tempFilePath = null;
try {
@ -160,7 +184,8 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
}
// Write the file to disk.
WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.context.getJobId());
long writestart = System.currentTimeMillis();
WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.jobId);
tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName());
ContentUtils.writeToFile(file, tempFilePath.toFile());
@ -184,7 +209,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
processAndSettings.redirectOutput(Redirect.appendTo(log));
int exitValue = ExecUtil.execute(processAndSettings, new FileIngestModuleProcessTerminator(this.context));
if (this.context.fileIngestIsCancelled() == true) {
// if it was cancelled by the user, result is OK
cleanup(outputDirPath, tempFilePath);
@ -211,21 +236,24 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
}
}
}
long writedelta = (System.currentTimeMillis() - writestart);
totals.totalWritetime.addAndGet(writedelta);
// Now that we've cleaned up the folders and data files, parse the xml output file to add carved items into the database
long calcstart = System.currentTimeMillis();
PhotoRecCarverOutputParser parser = new PhotoRecCarverOutputParser(outputDirPath);
List<LayoutFile> theList = parser.parse(newAuditFile, id, file);
if (theList != null) { // if there were any results from carving, add the unallocated carving event to the reports list.
context.addFilesToJob(new ArrayList<>(theList));
services.fireModuleContentEvent(new ModuleContentEvent(theList.get(0))); // fire an event to update the tree
List<LayoutFile> carvedItems = parser.parse(newAuditFile, id, file);
long calcdelta = (System.currentTimeMillis() - calcstart);
totals.totalParsetime.addAndGet(calcdelta);
if (carvedItems != null) { // if there were any results from carving, add the unallocated carving event to the reports list.
totals.totalItemsRecovered.addAndGet(carvedItems.size());
context.addFilesToJob(new ArrayList<>(carvedItems));
services.fireModuleContentEvent(new ModuleContentEvent(carvedItems.get(0))); // fire an event to update the tree
}
}
catch (IOException ex) {
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error processing " + file.getName() + " with PhotoRec carver", ex); // NON-NLS
return IngestModule.ProcessResult.ERROR;
}
finally {
} finally {
if (null != tempFilePath && Files.exists(tempFilePath)) {
// Get rid of the unallocated space file.
tempFilePath.toFile().delete();
@ -234,7 +262,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
return IngestModule.ProcessResult.OK;
}
private void cleanup(Path outputDirPath, Path tempFilePath) {
// cleanup the output path
FileUtil.deleteDir(new File(outputDirPath.toString()));
@ -243,19 +271,48 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
}
}
private synchronized void postSummary() {
IngestJobTotals jobTotals = totalsForIngestJobs.remove(jobId);
StringBuilder detailsSb = new StringBuilder();
//details
detailsSb.append("<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
*/
@Override
public void shutDown() {
if (this.context != null && refCounter.decrementAndGet(this.context.getJobId()) == 0) {
if (this.context != null && refCounter.decrementAndGet(this.jobId) == 0) {
try {
// The last instance of this module for an ingest job cleans out
// the working paths map entry for the job and deletes the temp dir.
WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.context.getJobId());
WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.jobId);
FileUtil.deleteDir(new File(paths.getTempDirPath().toString()));
}
postSummary();
}
catch (SecurityException ex) {
logger.log(Level.SEVERE, "Error shutting down PhotoRec carver module", ex); // NON-NLS
}

View File

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

View File

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

View File

@ -26,6 +26,7 @@ import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -80,6 +81,10 @@ public enum FileTypeUtils {
*/
private static FileTypeDetector FILE_TYPE_DETECTOR;
private static final String IMAGE_GIF_MIME = "image/gif";
private static final TreeSet<String> GIF_MIME_SET = new TreeSet<>(Arrays.asList(IMAGE_GIF_MIME));
/**
* static initalizer block to initialize sets of extensions and mimetypes
* to be supported
@ -164,7 +169,7 @@ public enum FileTypeUtils {
*
* @return true if this file is supported or false if not
*/
public static Boolean isDrawable(AbstractFile file) {
public static boolean isDrawable(AbstractFile file) {
return hasDrawableMimeType(file).orElseGet(() -> {
final boolean contains = FileTypeUtils.supportedExtensions.contains(file.getNameExtension());
final boolean jpegFileHeader = ImageUtils.isJpegFileHeader(file);
@ -175,6 +180,30 @@ public enum FileTypeUtils {
});
}
public static boolean isGIF(AbstractFile file) {
try {
final FileTypeDetector fileTypeDetector = getFileTypeDetector();
if (nonNull(fileTypeDetector)) {
String fileType = fileTypeDetector.getFileType(file);
return IMAGE_GIF_MIME.equalsIgnoreCase(fileType);
}
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "Failed to get mime type with FileTypeDetector.", ex);
}
LOGGER.log(Level.WARNING, "Falling back on direct mime type check.");
switch (file.isMimeType(GIF_MIME_SET)) {
case TRUE:
return true;
case UNDEFINED:
LOGGER.log(Level.WARNING, "Falling back on extension check.");
return "gif".equals(file.getNameExtension());
case FALSE:
default:
return false;
}
}
/**
* does the given file have drawable/supported mime type
*

View File

@ -462,7 +462,7 @@ public final class ImageGalleryController {
//this file should be included and we don't already know about it from hash sets (NSRL)
queueDBWorkerTask(new UpdateFileTask(file, db));
} else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
//doing this check results in fewer tasks queued up, and faster completion of db update
//doing this check results in fewer tasks queued up, and faster completion of db update
//this file would have gotten scooped up in initial grab, but actually we don't need it
queueDBWorkerTask(new RemoveFileTask(file, db));
}
@ -751,9 +751,9 @@ public final class ImageGalleryController {
"' or name LIKE '%.")
+ "')";
static private final String MIMETYPE_CLAUSE
= "blackboard_attributes.value_text LIKE "
= "blackboard_attributes.value_text LIKE '"
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
" OR blackboard_attributes.value_text LIKE ");
"' OR blackboard_attributes.value_text LIKE '") + "' ";
static private final String DRAWABLE_QUERY = FILE_EXTESNION_CLAUSE + " OR tsk_files.obj_id IN ("
+ "SELECT tsk_files.obj_id from tsk_files , blackboard_artifacts, blackboard_attributes"

View File

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

View File

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

View File

@ -18,10 +18,12 @@
*/
package org.sleuthkit.autopsy.imagegallery.datamodel;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -37,14 +39,10 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.ReadContentInputStream;
@ -90,6 +88,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
}
}
SoftReference<Image> imageRef;
private String drawablePath;
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) {
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.IOException;
import java.lang.ref.SoftReference;
import java.util.Objects;
import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import javax.imageio.ImageIO;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream;
@ -45,22 +44,20 @@ public class ImageFile<T extends AbstractFile> extends DrawableFile<T> {
ImageIO.scanForPlugins();
}
private SoftReference<Image> imageRef;
ImageFile(T f, Boolean analyzed) {
super(f, analyzed);
}
@Override
public Image getThumbnail() {
return ThumbnailCache.getDefault().get(this);
}
@Override
public Image getFullSizeImage() {
Image image = null;
if (imageRef != null) {
image = imageRef.get();
Image image = (imageRef != null) ? imageRef.get() : null;
if (image == null || image.isError()) {
if (FileTypeUtils.isGIF(file)) {
//directly read gif to preserve potential animation,
image = new Image(new BufferedInputStream(new ReadContentInputStream(file)));
}
}
if (image == null || image.isError()) {
try (BufferedInputStream readContentInputStream = new BufferedInputStream(new ReadContentInputStream(this.getAbstractFile()))) {
@ -75,12 +72,6 @@ public class ImageFile<T extends AbstractFile> extends DrawableFile<T> {
return image;
}
@Override
public boolean isDisplayable() {
Image thumbnail = getThumbnail();
return Objects.nonNull(thumbnail) && thumbnail.errorProperty().get() == false;
}
@Override
Double getWidth() {
final Image fullSizeImage = getFullSizeImage();

View File

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

View File

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

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
@ -27,7 +26,6 @@ import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
@ -46,7 +44,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
public class MediaControl extends BorderPane {
public class VideoPlayer extends BorderPane {
private static final Image VOLUME_HIGH = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume.png");
private static final Image VOLUME_LOW = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume-low.png");
@ -107,21 +105,6 @@ public class MediaControl extends BorderPane {
};
private final VideoFile<?> file;
public static Node create(VideoFile<?> file) {
try {
return new MediaControl(new MediaPlayer(file.getMedia()), file);
} catch (IOException ex) {
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "failed to initialize MediaControl for file " + file.getName(), ex);
return new Text(ex.getLocalizedMessage() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action.");
} catch (MediaException ex) {
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, ex.getType() + " Failed to initialize MediaControl for file " + file.getName(), ex);
return new Text(ex.getType() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action.");
} catch (OutOfMemoryError ex) {
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "failed to initialize MediaControl for file " + file.getName(), ex);
return new Text("There was a problem playing video file.\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action.");
}
}
@FXML
void initialize() {
assert controlButton != null : "fx:id=\"controlButton\" was not injected: check your FXML file 'MediaControl.fxml'.";
@ -250,7 +233,7 @@ public class MediaControl extends BorderPane {
}
}
private MediaControl(MediaPlayer mp, VideoFile<?> file) {
public VideoPlayer(MediaPlayer mp, VideoFile<?> file) {
this.file = file;
this.mp = mp;
FXMLConstructor.construct(this, "MediaControl.fxml");

View File

@ -26,12 +26,10 @@ import javafx.scene.CacheHint;
import javafx.scene.control.Control;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableTileBase.globalSelectionModel;
import org.sleuthkit.datamodel.AbstractContent;
@ -50,17 +48,6 @@ public class DrawableTile extends DrawableTileBase {
private static final Logger LOGGER = Logger.getLogger(DrawableTile.class.getName());
/**
* the central ImageView that shows a thumbnail of the represented file
*/
@FXML
private ImageView imageView;
@Override
protected void disposeContent() {
//no-op
}
@FXML
@Override
protected void initialize() {
@ -90,12 +77,6 @@ public class DrawableTile extends DrawableTileBase {
FXMLConstructor.construct(this, "DrawableTile.fxml");
}
@Override
@ThreadConfined(type = ThreadType.JFX)
protected void clearContent() {
imageView.setImage(null);
}
/**
* {@inheritDoc }
*/
@ -109,21 +90,13 @@ public class DrawableTile extends DrawableTileBase {
}
@Override
protected Runnable getContentUpdateRunnable() {
if (getFile().isPresent()) {
Image image = getFile().get().getThumbnail();
return () -> {
imageView.setImage(image);
};
} else {
return () -> { //no-op
};
}
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
return new ThumbnailLoaderTask(file);
}
@Override
protected String getTextForLabel() {
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.corecomponentinterfaces.ContextMenuActionsProvider;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@ -71,6 +69,7 @@ import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
@ -106,7 +105,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@FXML
protected ImageView undisplayableImageView;
** displays the icon representing follow up tag */
/** displays the icon representing follow up tag */
@FXML
private ImageView followUpImageView;
@ -120,8 +119,13 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@FXML
Label nameLabel;
/** the groupPane this {@link DrawableTileBase} is embedded in */
@FXML
protected ImageView imageView;
/**
* the groupPane this {@link DrawableTileBase} is embedded in
*/
final private GroupPane groupPane;
volatile private boolean registered = false;
protected DrawableTileBase(GroupPane groupPane) {
@ -235,13 +239,6 @@ public abstract class DrawableTileBase extends DrawableUIBase {
return groupPane;
}
@ThreadConfined(type = ThreadType.UI)
protected abstract void clearContent();
protected abstract void disposeContent();
protected abstract Runnable getContentUpdateRunnable();
protected abstract String getTextForLabel();
protected void initialize() {
@ -279,6 +276,8 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@Override
synchronized protected void setFileHelper(final Long newFileID) {
setFileIDOpt(Optional.ofNullable(newFileID));
setFileOpt(Optional.empty());
disposeContent();
if (getFileID().isPresent() == false || Case.isCaseOpen() == false) {
@ -287,23 +286,18 @@ public abstract class DrawableTileBase extends DrawableUIBase {
getController().getTagsManager().unregisterListener(this);
registered = false;
}
setFileOpt(Optional.empty());
Platform.runLater(() -> {
clearContent();
});
updateContent();
} else {
if (registered == false) {
getController().getCategoryManager().registerListener(this);
getController().getTagsManager().registerListener(this);
registered = true;
}
setFileOpt(Optional.empty());
updateSelectionState();
updateCategory();
updateFollowUpIcon();
updateUI();
Platform.runLater(getContentUpdateRunnable());
updateContent();
}
}
@ -311,7 +305,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
getFile().ifPresent(file -> {
final boolean isVideo = file.isVideo();
final boolean hasHashSetHits = hasHashHit();
final boolean isUndisplayable = file.isDisplayable() == false;
final boolean isUndisplayable = (isVideo ? ((VideoFile<?>) file).isDisplayableAsMedia() : file.isDisplayableAsImage()) == false;
final String text = getTextForLabel();
Platform.runLater(() -> {
@ -387,4 +381,40 @@ public abstract class DrawableTileBase extends DrawableUIBase {
followUpToggle.setSelected(hasFollowUp);
});
}
// @Override
// Node getContentNode() {
// if (getFile().isPresent() == false) {
// imageCache = null;
// Platform.runLater(() -> {
// imageView.setImage(null);
// });
// return null;
// } else {
// Image thumbnail = isNull(imageCache) ? null : imageCache.get();
//
// if (nonNull(thumbnail)) {
// Platform.runLater(() -> {
// imageView.setImage(thumbnail);
// });
// return imageView;
// } else {
// DrawableFile<?> file = getFile().get();
//
// if (isNull(imageTask) || imageTask.isDone()) {
// imageTask = new ImageLoadTask(file) {
//
// @Override
// void saveToCache(Image result) {
// synchronized (DrawableTileBase.this) {
// imageCache = new SoftReference<>(result);
// }
// }
// };
// new Thread(imageTask).start();
// }
// return getLoadingProgressIndicator();
// }
// }
// }
}

View File

@ -18,11 +18,22 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.lang.ref.SoftReference;
import java.util.Objects;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@ -33,11 +44,21 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName());
@FXML
protected BorderPane imageBorder;
@FXML
protected ImageView imageView;
private final ImageGalleryController controller;
private Optional<DrawableFile<?>> fileOpt = Optional.empty();
private Optional<Long> fileIDOpt = Optional.empty();
private Task<Image> imageTask;
private SoftReference<Image> imageCache;
private ProgressIndicator progressIndicator;
public DrawableUIBase(ImageGalleryController controller) {
this.controller = controller;
@ -86,10 +107,133 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
synchronized public void setFile(Long newFileID) {
if (getFileID().isPresent()) {
if (Objects.equals(newFileID, getFileID().get()) == false) {
setFileHelper(newFileID);
if (Objects.nonNull(newFileID)) {
setFileHelper(newFileID);
}
}
} else if (nonNull(newFileID)) {
} else if (Objects.nonNull(newFileID)) {
setFileHelper(newFileID);
}
}
synchronized protected void updateContent() {
Node content = getContentNode();
Platform.runLater(() -> {
imageBorder.setCenter(content);
});
}
synchronized protected void disposeContent() {
if (imageTask != null) {
imageTask.cancel(true);
}
imageTask = null;
imageCache = null;
}
ProgressIndicator getLoadingProgressIndicator() {
if (progressIndicator == null) {
progressIndicator = new ProgressIndicator();
}
return progressIndicator;
}
Node getContentNode() {
if (getFile().isPresent() == false) {
imageCache = null;
Platform.runLater(() -> {
if (imageView != null) {
imageView.setImage(null);
}
});
return null;
} else {
Image thumbnail = isNull(imageCache) ? null : imageCache.get();
if (nonNull(thumbnail)) {
Platform.runLater(() -> {
if (imageView != null) {
imageView.setImage(thumbnail);
}
});
return imageView;
} else {
DrawableFile<?> file = getFile().get();
if (isNull(imageTask)) {
imageTask = getNewImageLoadTask(file);
new Thread(imageTask).start();
} else if (imageTask.isDone()) {
return null;
}
return getLoadingProgressIndicator();
}
}
}
abstract CachedLoaderTask<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

@ -113,7 +113,7 @@
</ImageView>
</graphic>
</ToggleButton>
<ToggleButton id="filmStripToggle" fx:id="slideShowToggle" contentDisplay="GRAPHIC_ONLY" graphicTextGap="0.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="32.0" mnemonicParsing="false" prefWidth="-1.0" text="">
<ToggleButton id="filmStripToggle" fx:id="slideShowToggle" contentDisplay="GRAPHIC_ONLY" graphicTextGap="0.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="32.0" mnemonicParsing="false" prefWidth="-1.0" text="">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" rotate="90.0" translateY="1.0">
<image>

View File

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

View File

@ -13,7 +13,7 @@
<children>
<VBox alignment="TOP_CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<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>
<ImageView fx:id="imageView" fitHeight="200.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</center>

View File

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

View File

@ -127,7 +127,10 @@
</BorderPane>
</bottom>
<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>
</BorderPane>
</children>

View File

@ -18,21 +18,27 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.function.Function;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import static javafx.scene.input.KeyCode.LEFT;
import static javafx.scene.input.KeyCode.RIGHT;
@ -46,6 +52,10 @@ import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.media.Media;
import javafx.scene.media.MediaException;
import javafx.scene.media.MediaPlayer;
import javafx.scene.text.Text;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
@ -56,10 +66,9 @@ import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.ImageFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import org.sleuthkit.autopsy.imagegallery.gui.MediaControl;
import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
@ -100,31 +109,35 @@ public class SlideShowView extends DrawableTileBase {
private ToolBar toolBar;
@FXML
private BorderPane footer;
private Task<Node> mediaTask;
SlideShowView(GroupPane gp) {
super(gp);
FXMLConstructor.construct(this, "SlideShow.fxml");
FXMLConstructor.construct(this, "SlideShowView.fxml");
}
@FXML
@Override
protected void initialize() {
super.initialize();
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert leftButton != null : "fx:id=\"leftButton\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert rightButton != null : "fx:id=\"rightButton\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert tagSplitButton != null : "fx:id=\"tagSplitButton\" was not injected: check your FXML file 'SlideShow.fxml'.";
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert leftButton != null : "fx:id=\"leftButton\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert rightButton != null : "fx:id=\"rightButton\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert tagSplitButton != null : "fx:id=\"tagSplitButton\" was not injected: check your FXML file 'SlideShowView.fxml'.";
Platform.runLater(() -> {
HBox.setHgrow(spring, Priority.ALWAYS);
spring.setMinWidth(Region.USE_PREF_SIZE);
});
imageView.fitWidthProperty().bind(imageBorder.widthProperty().subtract(CAT_BORDER_WIDTH * 2));
imageView.fitHeightProperty().bind(heightProperty().subtract(CAT_BORDER_WIDTH * 4).subtract(footer.heightProperty()).subtract(toolBar.heightProperty()));
tagSplitButton.setOnAction((ActionEvent t) -> {
try {
GuiUtils.createSelTagMenuItem(getController().getTagsManager().getFollowUpTagName(), tagSplitButton, getController()).getOnAction().handle(t);
@ -195,8 +208,8 @@ public class SlideShowView extends DrawableTileBase {
getGroupPane().grouping().addListener((Observable observable) -> {
syncButtonVisibility();
if (getGroupPane().getGrouping() != null) {
getGroupPane().getGrouping().fileIds().addListener((Observable observable1) -> {
if (getGroupPane().getGroup() != null) {
getGroupPane().getGroup().fileIds().addListener((Observable observable1) -> {
syncButtonVisibility();
});
}
@ -206,7 +219,7 @@ public class SlideShowView extends DrawableTileBase {
@ThreadConfined(type = ThreadType.ANY)
private void syncButtonVisibility() {
try {
final boolean hasMultipleFiles = getGroupPane().getGrouping().fileIds().size() > 1;
final boolean hasMultipleFiles = getGroupPane().getGroup().fileIds().size() > 1;
Platform.runLater(() -> {
rightButton.setVisible(hasMultipleFiles);
leftButton.setVisible(hasMultipleFiles);
@ -221,8 +234,8 @@ public class SlideShowView extends DrawableTileBase {
@ThreadConfined(type = ThreadType.JFX)
public void stopVideo() {
if (imageBorder.getCenter() instanceof MediaControl) {
((MediaControl) imageBorder.getCenter()).stopVideo();
if (imageBorder.getCenter() instanceof VideoPlayer) {
((VideoPlayer) imageBorder.getCenter()).stopVideo();
}
}
@ -239,40 +252,40 @@ public class SlideShowView extends DrawableTileBase {
@Override
protected void disposeContent() {
stopVideo();
}
@Override
@ThreadConfined(type = ThreadType.UI)
protected void clearContent() {
stopVideo();
imageBorder.setCenter(null);
super.disposeContent();
if (mediaTask != null) {
mediaTask.cancel(true);
}
mediaTask = null;
mediaCache = null;
}
private SoftReference<Node> mediaCache;
/** {@inheritDoc } */
@Override
protected Runnable getContentUpdateRunnable() {
return getFile().map(new Function<DrawableFile<?>, Runnable>() {
@Override
public Runnable apply(DrawableFile<?> file) {
if (file.isVideo()) {
return () -> {
imageBorder.setCenter(MediaControl.create((VideoFile<?>) file));
};
Node getContentNode() {
if (getFile().isPresent() == false) {
mediaCache = null;
return super.getContentNode();
} else {
DrawableFile<?> file = getFile().get();
if (file.isVideo()) {
Node mediaNode = (isNull(mediaCache)) ? null : mediaCache.get();
if (nonNull(mediaNode)) {
return mediaNode;
} else {
ImageView imageView = new ImageView(((ImageFile<?>) file).getFullSizeImage());
imageView.setPreserveRatio(true);
imageView.fitWidthProperty().bind(imageBorder.widthProperty().subtract(CAT_BORDER_WIDTH * 2));
imageView.fitHeightProperty().bind(heightProperty().subtract(CAT_BORDER_WIDTH * 4).subtract(footer.heightProperty()).subtract(toolBar.heightProperty()));
return () -> {
imageBorder.setCenter(imageView);
};
if (isNull(mediaTask)) {
mediaTask = new MediaLoadTask(((VideoFile<?>) file));
new Thread(mediaTask).start();
} else if (mediaTask.isDone()) {
return null;
}
return getLoadingProgressIndicator();
}
}
}).orElse(() -> {
});
return super.getContentNode();
}
}
/** {@inheritDoc } */
@ -292,12 +305,12 @@ public class SlideShowView extends DrawableTileBase {
@ThreadConfined(type = ThreadType.JFX)
synchronized private void cycleSlideShowImage(int direction) {
stopVideo();
final int groupSize = getGroupPane().getGrouping().fileIds().size();
final int groupSize = getGroupPane().getGroup().fileIds().size();
final Integer nextIndex = getFileID().map(fileID -> {
final int currentIndex = getGroupPane().getGrouping().fileIds().indexOf(fileID);
final int currentIndex = getGroupPane().getGroup().fileIds().indexOf(fileID);
return (currentIndex + direction + groupSize) % groupSize;
}).orElse(0);
setFile(getGroupPane().getGrouping().fileIds().get(nextIndex)
setFile(getGroupPane().getGroup().fileIds().get(nextIndex)
);
}
@ -306,7 +319,7 @@ public class SlideShowView extends DrawableTileBase {
* of y"
*/
private String getSupplementalText() {
final ObservableList<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 )")
.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.importOwConflict=Import list conflict
KeywordSearch.kwListFailImportMsg=Keyword list not imported
KeywordSearchListsManagementPanel.fileExtensionFilterLbl=Keyword List File
KeywordSearchListsManagementPanel.fileExtensionFilterLbl=Autopsy Keyword List File (xml)
KeywordSearchListsManagementPanel.fileExtensionFilterLb2=Encase Keyword List File (txt)
KeywordSearch.listImportFeatureTitle=Keyword List Import
KeywordSearchIngestModule.moduleName=Keyword Search
KeywordSearchIngestModule.moduleDescription=Performs file indexing and periodic search using keywords and regular expressions in lists.

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
JFileChooser chooser = new JFileChooser();
final String[] EXTENSION = new String[]{"xml", "txt"}; //NON-NLS
FileNameExtensionFilter filter = new FileNameExtensionFilter(
NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.fileExtensionFilterLbl"), EXTENSION);
chooser.setFileFilter(filter);
final String[] AUTOPSY_EXTENSIONS = new String[]{"xml"}; //NON-NLS
final String[] ENCASE_EXTENSIONS = new String[]{"txt"}; //NON-NLS
FileNameExtensionFilter autopsyFilter = new FileNameExtensionFilter(
NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.fileExtensionFilterLbl"), AUTOPSY_EXTENSIONS);
FileNameExtensionFilter encaseFilter = new FileNameExtensionFilter(
NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.fileExtensionFilterLb2"), ENCASE_EXTENSIONS);
chooser.addChoosableFileFilter(autopsyFilter);
chooser.addChoosableFileFilter(encaseFilter);
chooser.setAcceptAllFileFilterUsed(false);
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
String listName = null;

View File

@ -1,5 +1,5 @@
#Updated by build script
#Wed, 15 Apr 2015 18:11:08 -0400
#Tue, 28 Jul 2015 13:44:22 -0400
LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314
SPLASH_WIDTH=538
@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18
SplashRunningTextColor=0x0
SplashRunningTextFontSize=19
currentVersion=Autopsy 3.1.2
currentVersion=Autopsy 3.1.3

View File

@ -1,5 +1,5 @@
#Updated by build script
#Wed, 15 Apr 2015 18:11:08 -0400
#Tue, 28 Jul 2015 13:44:22 -0400
CTL_MainWindow_Title=Autopsy 3.1.2
CTL_MainWindow_Title_No_Project=Autopsy 3.1.2
CTL_MainWindow_Title=Autopsy 3.1.3
CTL_MainWindow_Title_No_Project=Autopsy 3.1.3

View File

@ -1,4 +1,5 @@
branding.token=autopsy
nbjdk.active=JDK_1.8u40x64
# Version of platform that is automatically downloaded
# Note build.xml has similar definitions that should be kept in sync (manually)
netbeans-plat-version=7.3.1
@ -13,111 +14,7 @@ cluster.path=\
${nbplatform.active.dir}/java:\
${nbplatform.active.dir}/platform
disabled.modules=\
org.apache.tools.ant.module,\
org.netbeans.api.debugger.jpda,\
org.netbeans.api.java,\
org.netbeans.lib.nbjavac,\
org.netbeans.libs.cglib,\
org.netbeans.libs.javacapi,\
org.netbeans.libs.javacimpl,\
org.netbeans.libs.springframework,\
org.netbeans.modules.ant.browsetask,\
org.netbeans.modules.ant.debugger,\
org.netbeans.modules.ant.freeform,\
org.netbeans.modules.ant.grammar,\
org.netbeans.modules.ant.kit,\
org.netbeans.modules.beans,\
org.netbeans.modules.classfile,\
org.netbeans.modules.dbschema,\
org.netbeans.modules.debugger.jpda,\
org.netbeans.modules.debugger.jpda.ant,\
org.netbeans.modules.debugger.jpda.kit,\
org.netbeans.modules.debugger.jpda.projects,\
org.netbeans.modules.debugger.jpda.ui,\
org.netbeans.modules.debugger.jpda.visual,\
org.netbeans.modules.findbugs.installer,\
org.netbeans.modules.form,\
org.netbeans.modules.form.binding,\
org.netbeans.modules.form.j2ee,\
org.netbeans.modules.form.kit,\
org.netbeans.modules.form.nb,\
org.netbeans.modules.form.refactoring,\
org.netbeans.modules.hibernate,\
org.netbeans.modules.hibernatelib,\
org.netbeans.modules.hudson.ant,\
org.netbeans.modules.hudson.maven,\
org.netbeans.modules.i18n,\
org.netbeans.modules.i18n.form,\
org.netbeans.modules.j2ee.core.utilities,\
org.netbeans.modules.j2ee.eclipselink,\
org.netbeans.modules.j2ee.eclipselinkmodelgen,\
org.netbeans.modules.j2ee.jpa.refactoring,\
org.netbeans.modules.j2ee.jpa.verification,\
org.netbeans.modules.j2ee.metadata,\
org.netbeans.modules.j2ee.metadata.model.support,\
org.netbeans.modules.j2ee.persistence,\
org.netbeans.modules.j2ee.persistence.kit,\
org.netbeans.modules.j2ee.persistenceapi,\
org.netbeans.modules.java.api.common,\
org.netbeans.modules.java.debug,\
org.netbeans.modules.java.editor,\
org.netbeans.modules.java.editor.lib,\
org.netbeans.modules.java.examples,\
org.netbeans.modules.java.freeform,\
org.netbeans.modules.java.guards,\
org.netbeans.modules.java.helpset,\
org.netbeans.modules.java.hints,\
org.netbeans.modules.java.hints.declarative,\
org.netbeans.modules.java.hints.declarative.test,\
org.netbeans.modules.java.hints.legacy.spi,\
org.netbeans.modules.java.hints.test,\
org.netbeans.modules.java.hints.ui,\
org.netbeans.modules.java.j2seplatform,\
org.netbeans.modules.java.j2seproject,\
org.netbeans.modules.java.kit,\
org.netbeans.modules.java.lexer,\
org.netbeans.modules.java.navigation,\
org.netbeans.modules.java.platform,\
org.netbeans.modules.java.preprocessorbridge,\
org.netbeans.modules.java.project,\
org.netbeans.modules.java.source,\
org.netbeans.modules.java.source.ant,\
org.netbeans.modules.java.source.queries,\
org.netbeans.modules.java.source.queriesimpl,\
org.netbeans.modules.java.sourceui,\
org.netbeans.modules.java.testrunner,\
org.netbeans.modules.javadoc,\
org.netbeans.modules.javawebstart,\
org.netbeans.modules.jellytools.java,\
org.netbeans.modules.junit,\
org.netbeans.modules.maven,\
org.netbeans.modules.maven.checkstyle,\
org.netbeans.modules.maven.coverage,\
org.netbeans.modules.maven.embedder,\
org.netbeans.modules.maven.grammar,\
org.netbeans.modules.maven.graph,\
org.netbeans.modules.maven.hints,\
org.netbeans.modules.maven.indexer,\
org.netbeans.modules.maven.junit,\
org.netbeans.modules.maven.kit,\
org.netbeans.modules.maven.model,\
org.netbeans.modules.maven.osgi,\
org.netbeans.modules.maven.persistence,\
org.netbeans.modules.maven.refactoring,\
org.netbeans.modules.maven.repository,\
org.netbeans.modules.maven.search,\
org.netbeans.modules.maven.spring,\
org.netbeans.modules.projectimport.eclipse.core,\
org.netbeans.modules.projectimport.eclipse.j2se,\
org.netbeans.modules.refactoring.java,\
org.netbeans.modules.spellchecker.bindings.java,\
org.netbeans.modules.spring.beans,\
org.netbeans.modules.testng,\
org.netbeans.modules.testng.ant,\
org.netbeans.modules.testng.maven,\
org.netbeans.modules.websvc.jaxws21,\
org.netbeans.modules.websvc.jaxws21api,\
org.netbeans.modules.websvc.saas.codegen.java,\
org.netbeans.modules.xml.jaxb,\
org.netbeans.modules.xml.tools.java,\
org.netbeans.spi.java.hints
org.netbeans.modules.whitelist

View File

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

View File

@ -870,7 +870,9 @@ class TestResultsDiffer(object):
diff_path = os.path.splitext(os.path.basename(output_file))[0]
diff_path += "-Diff.txt"
diff_file = codecs.open(diff_path, "wb", "utf_8")
dffcmdlst = ["diff", output_file, gold_file]
# Gold needs to be passed in before output.
dffcmdlst = ["diff", gold_file, output_file]
subprocess.call(dffcmdlst, stdout = diff_file)
Errors.add_errors_out(diff_path)
@ -902,7 +904,8 @@ class TestResultsDiffer(object):
gold_report_path = test_data.get_html_report_path(DBType.GOLD)
output_report_path = test_data.get_html_report_path(DBType.OUTPUT)
try:
(subprocess.check_output(['diff', '-r', '-N', '-x', '*.png', '-x', '*.ico', '--ignore-matching-lines',
# Ensure gold is passed before output
(subprocess.check_output(["diff", '-r', '-N', '-x', '*.png', '-x', '*.ico', '--ignore-matching-lines',
'HTML Report Generated on \|Autopsy Report for case \|Case:\|Case Number:'
'\|Examiner:', gold_report_path, output_report_path]))
print_report("", "REPORT COMPARISON", "The test reports matched the gold reports")

View File

@ -139,6 +139,7 @@ class TskDbDiff(object):
# If they are different, invoke 'diff'
diff_file = codecs.open(diff_path, "wb", "utf_8")
# Gold needs to be passed in as 1st arg and output as 2nd
dffcmdlst = ["diff", gold_file, output_file]
subprocess.call(dffcmdlst, stdout = diff_file)