mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
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:
commit
5c4bd438e8
@ -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.
BIN
Core/release/modules/ext/opencv-248.jar
Normal file
BIN
Core/release/modules/ext/opencv-248.jar
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/amd64/opencv_ffmpeg248_64.dll
Normal file
BIN
Core/release/modules/lib/amd64/opencv_ffmpeg248_64.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/amd64/opencv_java248.dll
Normal file
BIN
Core/release/modules/lib/amd64/opencv_java248.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/i386/opencv_ffmpeg248.dll
Normal file
BIN
Core/release/modules/lib/i386/opencv_ffmpeg248.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/i386/opencv_java248.dll
Normal file
BIN
Core/release/modules/lib/i386/opencv_java248.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/i586/opencv_ffmpeg248_64.dll
Normal file
BIN
Core/release/modules/lib/i586/opencv_ffmpeg248_64.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/i586/opencv_java248.dll
Normal file
BIN
Core/release/modules/lib/i586/opencv_java248.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/i686/opencv_ffmpeg248_64.dll
Normal file
BIN
Core/release/modules/lib/i686/opencv_ffmpeg248_64.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/i686/opencv_java248.dll
Normal file
BIN
Core/release/modules/lib/i686/opencv_java248.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/x86/opencv_ffmpeg248.dll
Normal file
BIN
Core/release/modules/lib/x86/opencv_ffmpeg248.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/x86/opencv_java248.dll
Normal file
BIN
Core/release/modules/lib/x86/opencv_java248.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/x86_64/opencv_ffmpeg248_64.dll
Normal file
BIN
Core/release/modules/lib/x86_64/opencv_ffmpeg248_64.dll
Normal file
Binary file not shown.
BIN
Core/release/modules/lib/x86_64/opencv_java248.dll
Normal file
BIN
Core/release/modules/lib/x86_64/opencv_java248.dll
Normal file
Binary file not shown.
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="org.openide.explorer.view.IconView" name="iconView">
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
127
Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java
Normal file
127
Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/images/working_spinner.gif
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/working_spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
@ -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);
|
||||
|
@ -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
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
||||
}
|
||||
|
||||
|
@ -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");
|
@ -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("");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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; -fx-border-width: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; -fx-border-width: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>
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user