mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
Merge branch 'master' of https://github.com/sleuthkit/autopsy
This commit is contained in:
commit
d88dfeb2e5
@ -28,7 +28,6 @@ import org.openide.modules.ModuleInstall;
|
|||||||
import org.openide.windows.WindowManager;
|
import org.openide.windows.WindowManager;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper over Installers in packages in Core module This is the main
|
* Wrapper over Installers in packages in Core module This is the main
|
||||||
@ -40,56 +39,6 @@ public class Installer extends ModuleInstall {
|
|||||||
private static final Logger logger = Logger.getLogger(Installer.class.getName());
|
private static final Logger logger = Logger.getLogger(Installer.class.getName());
|
||||||
private volatile boolean javaFxInit = true;
|
private volatile boolean javaFxInit = true;
|
||||||
|
|
||||||
static {
|
|
||||||
loadDynLibraries();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void loadDynLibraries() {
|
|
||||||
if (PlatformUtil.isWindowsOS()) {
|
|
||||||
try {
|
|
||||||
//on windows force loading ms crt dependencies first
|
|
||||||
//in case linker can't find them on some systems
|
|
||||||
//Note: if shipping with a different CRT version, this will only print a warning
|
|
||||||
//and try to use linker mechanism to find the correct versions of libs.
|
|
||||||
//We should update this if we officially switch to a new version of CRT/compiler
|
|
||||||
System.loadLibrary("msvcr100");
|
|
||||||
System.loadLibrary("msvcp100");
|
|
||||||
logger.log(Level.INFO, "MS CRT libraries loaded");
|
|
||||||
} catch (UnsatisfiedLinkError e) {
|
|
||||||
logger.log(Level.SEVERE, "Error loading ms crt libraries, ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
System.loadLibrary("zlib");
|
|
||||||
logger.log(Level.INFO, "ZLIB library loaded loaded");
|
|
||||||
} catch (UnsatisfiedLinkError e) {
|
|
||||||
logger.log(Level.SEVERE, "Error loading ZLIB library, ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
System.loadLibrary("libewf");
|
|
||||||
logger.log(Level.INFO, "EWF library loaded");
|
|
||||||
} catch (UnsatisfiedLinkError e) {
|
|
||||||
logger.log(Level.SEVERE, "Error loading EWF library, ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We should rename the Windows dll, to remove the lib prefix.
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
String tskLibName = null;
|
|
||||||
if (PlatformUtil.isWindowsOS()) {
|
|
||||||
tskLibName = "libtsk_jni";
|
|
||||||
} else {
|
|
||||||
tskLibName = "tsk_jni";
|
|
||||||
}
|
|
||||||
System.loadLibrary(tskLibName);
|
|
||||||
logger.log(Level.INFO, "TSK_JNI library loaded");
|
|
||||||
} catch (UnsatisfiedLinkError e) {
|
|
||||||
logger.log(Level.SEVERE, "Error loading tsk_jni library", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Installer() {
|
public Installer() {
|
||||||
javaFxInit = true;
|
javaFxInit = true;
|
||||||
packageInstallers = new ArrayList<ModuleInstall>();
|
packageInstallers = new ArrayList<ModuleInstall>();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2011 Basis Technology Corp.
|
* Copyright 2011-2013 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -34,7 +34,7 @@ import java.util.logging.Level;
|
|||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JTextPane;
|
import javax.swing.JTextPane;
|
||||||
import javax.swing.text.Document;
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.text.html.HTMLEditorKit;
|
import javax.swing.text.html.HTMLEditorKit;
|
||||||
import javax.swing.text.html.StyleSheet;
|
import javax.swing.text.html.StyleSheet;
|
||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
@ -56,7 +56,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
private static int currentPage = 1;
|
private static int currentPage = 1;
|
||||||
private List<BlackboardArtifact> artifacts;
|
private List<BlackboardArtifact> artifacts;
|
||||||
private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName());
|
private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName());
|
||||||
|
private final static String WAIT_TEXT = "Preparing display...";
|
||||||
|
|
||||||
/** Creates new form DataContentViewerArtifact */
|
/** Creates new form DataContentViewerArtifact */
|
||||||
public DataContentViewerArtifact() {
|
public DataContentViewerArtifact() {
|
||||||
initComponents();
|
initComponents();
|
||||||
@ -204,12 +205,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
|
|
||||||
private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
|
private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
|
||||||
currentPage = currentPage+1;
|
currentPage = currentPage+1;
|
||||||
setDataView(artifacts, currentPage);
|
new DisplayTask(currentPage).execute();
|
||||||
}//GEN-LAST:event_nextPageButtonActionPerformed
|
}//GEN-LAST:event_nextPageButtonActionPerformed
|
||||||
|
|
||||||
private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
|
private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
|
||||||
currentPage = currentPage-1;
|
currentPage = currentPage-1;
|
||||||
setDataView(artifacts, currentPage);
|
new DisplayTask(currentPage).execute();
|
||||||
}//GEN-LAST:event_prevPageButtonActionPerformed
|
}//GEN-LAST:event_prevPageButtonActionPerformed
|
||||||
|
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
@ -239,20 +240,30 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
Content content = lookup.lookup(Content.class);
|
Content content = lookup.lookup(Content.class);
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
resetComponent();
|
resetComponent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.setDataView(content.getAllArtifacts(), 1);
|
artifacts = content.getAllArtifacts();
|
||||||
} catch (TskException ex) {
|
}
|
||||||
logger.log(Level.WARNING, "Couldn't get artifacts: ", ex);
|
catch (TskException ex) {
|
||||||
|
logger.log(Level.WARNING, "Couldn't get artifacts", ex);
|
||||||
|
resetComponent();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus on a specific artifact if it is in the node
|
// focus on a specific artifact if it is in the node
|
||||||
|
int index = 0;
|
||||||
BlackboardArtifact artifact = lookup.lookup(BlackboardArtifact.class);
|
BlackboardArtifact artifact = lookup.lookup(BlackboardArtifact.class);
|
||||||
if (artifact != null) {
|
if (artifact != null) {
|
||||||
this.setSelectedArtifact(artifact);
|
index = artifacts.indexOf(artifact);
|
||||||
}
|
if (index == -1) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A little bit of cleverness here - add one since setDataView() is also passed page numbers.
|
||||||
|
new DisplayTask(index + 1).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -279,7 +290,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
public void resetComponent() {
|
public void resetComponent() {
|
||||||
// clear / reset the fields
|
// clear / reset the fields
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
this.artifacts = new ArrayList<BlackboardArtifact>();
|
this.artifacts = new ArrayList<>();
|
||||||
currentPageLabel.setText("");
|
currentPageLabel.setText("");
|
||||||
totalPageLabel.setText("");
|
totalPageLabel.setText("");
|
||||||
outputViewPane.setText("");
|
outputViewPane.setText("");
|
||||||
@ -311,12 +322,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArtifactStringContent artifact = node.getLookup().lookup(ArtifactStringContent.class);
|
Content content = node.getLookup().lookup(Content.class);
|
||||||
Content content = node.getLookup().lookup(Content.class);
|
|
||||||
|
|
||||||
if(artifact != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if(content != null) {
|
if(content != null) {
|
||||||
try {
|
try {
|
||||||
long size = content.getAllArtifactsCount();
|
long size = content.getAllArtifactsCount();
|
||||||
@ -380,15 +386,14 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
* @param artifacts List of artifacts that could be displayed
|
* @param artifacts List of artifacts that could be displayed
|
||||||
* @param offset Index into the list for the artifact to display
|
* @param offset Index into the list for the artifact to display
|
||||||
*/
|
*/
|
||||||
private void setDataView(List<BlackboardArtifact> artifacts, int offset) {
|
private void setDataView(int offset) {
|
||||||
// change the cursor to "waiting cursor" for this operation
|
|
||||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
|
outputViewPane.setText(WAIT_TEXT);
|
||||||
|
|
||||||
if(artifacts.isEmpty()){
|
if(artifacts.isEmpty()){
|
||||||
resetComponent();
|
resetComponent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.artifacts = artifacts;
|
|
||||||
StringContent artifactString = new ArtifactStringContent(artifacts.get(offset-1));
|
StringContent artifactString = new ArtifactStringContent(artifacts.get(offset-1));
|
||||||
String text = artifactString.getString();
|
String text = artifactString.getString();
|
||||||
|
|
||||||
@ -406,16 +411,19 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
setComponentsVisibility(true);
|
setComponentsVisibility(true);
|
||||||
outputViewPane.moveCaretPosition(0);
|
outputViewPane.moveCaretPosition(0);
|
||||||
this.setCursor(null);
|
this.setCursor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private class DisplayTask extends SwingWorker<Integer, Void> {
|
||||||
* Set the displayed artifact to the specified one.
|
final int pageIndex;
|
||||||
* @param artifact Artifact to display
|
|
||||||
*/
|
DisplayTask(final int pageIndex) {
|
||||||
private void setSelectedArtifact(BlackboardArtifact artifact) {
|
this.pageIndex = pageIndex;
|
||||||
if(artifacts.contains(artifact)) {
|
|
||||||
int index = artifacts.indexOf(artifact);
|
|
||||||
setDataView(artifacts, index+1);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public Integer doInBackground() {
|
||||||
|
setDataView(pageIndex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||||
|
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
|
||||||
</AuxValues>
|
</AuxValues>
|
||||||
|
|
||||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
|
||||||
|
@ -43,7 +43,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
|||||||
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
|
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
|
||||||
|
|
||||||
private String[] IMAGES; // use javafx supported
|
private String[] IMAGES; // use javafx supported
|
||||||
private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"};
|
private static final String[] VIDEOS = new String[]{".swf", ".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"};
|
||||||
private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"};
|
private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"};
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
|
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
|
||||||
@ -65,7 +65,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
|||||||
|
|
||||||
initComponents();
|
initComponents();
|
||||||
|
|
||||||
videoPanel = new MediaViewVideoPanel();
|
// get the right panel for our platform
|
||||||
|
videoPanel = MediaViewVideoPanel.createVideoPanel();
|
||||||
|
|
||||||
imagePanel = new MediaViewImagePanel();
|
imagePanel = new MediaViewImagePanel();
|
||||||
videoPanelInited = videoPanel.isInited();
|
videoPanelInited = videoPanel.isInited();
|
||||||
imagePanelInited = imagePanel.isInited();
|
imagePanelInited = imagePanel.isInited();
|
||||||
@ -75,14 +77,14 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void customizeComponents() {
|
private void customizeComponents() {
|
||||||
logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
|
|
||||||
//initialize supported image types
|
//initialize supported image types
|
||||||
//TODO use mime-types instead once we have support
|
//TODO use mime-types instead once we have support
|
||||||
String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes();
|
String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes();
|
||||||
IMAGES = new String[fxSupportedImagesSuffixes.length];
|
IMAGES = new String[fxSupportedImagesSuffixes.length];
|
||||||
|
//logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
|
||||||
for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) {
|
for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) {
|
||||||
String suffix = fxSupportedImagesSuffixes[i];
|
String suffix = fxSupportedImagesSuffixes[i];
|
||||||
logger.log(Level.INFO, "suffix: " + suffix);
|
//logger.log(Level.INFO, "suffix: " + suffix);
|
||||||
IMAGES[i] = "." + suffix;
|
IMAGES[i] = "." + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,35 +112,38 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNode(Node selectedNode) {
|
public void setNode(Node selectedNode) {
|
||||||
if (selectedNode == null) {
|
try {
|
||||||
resetComponent();
|
if (selectedNode == null) {
|
||||||
return;
|
resetComponent();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class);
|
AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
resetComponent();
|
resetComponent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.equals(lastFile)) {
|
if (file.equals(lastFile)) {
|
||||||
return; //prevent from loading twice if setNode() called mult. times
|
return; //prevent from loading twice if setNode() called mult. times
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
resetComponent();
|
||||||
|
|
||||||
|
final Dimension dims = DataContentViewerMedia.this.getSize();
|
||||||
|
|
||||||
|
if (imagePanelInited && containsExt(file.getName(), IMAGES)) {
|
||||||
|
imagePanel.showImageFx(file, dims);
|
||||||
|
this.switchPanels(false);
|
||||||
|
} else if (videoPanelInited
|
||||||
|
&& (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) {
|
||||||
|
videoPanel.setupVideo(file, dims);
|
||||||
|
switchPanels(true);
|
||||||
|
}
|
||||||
lastFile = file;
|
lastFile = file;
|
||||||
}
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.SEVERE, "Exception while setting node", e);
|
||||||
videoPanel.reset();
|
}
|
||||||
|
|
||||||
final Dimension dims = DataContentViewerMedia.this.getSize();
|
|
||||||
|
|
||||||
if (imagePanelInited && containsExt(file.getName(), IMAGES)) {
|
|
||||||
imagePanel.showImageFx(file, dims);
|
|
||||||
this.switchPanels(false);
|
|
||||||
} else if (videoPanelInited
|
|
||||||
&& (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) {
|
|
||||||
videoPanel.setupVideo(file, dims);
|
|
||||||
switchPanels(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -177,12 +182,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetComponent() {
|
public void resetComponent() {
|
||||||
lastFile = null;
|
|
||||||
videoPanel.reset();
|
videoPanel.reset();
|
||||||
|
// @@@ Seems like we should also reset the image viewer...
|
||||||
|
lastFile = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported(Node node) {
|
public boolean isSupported(Node node) {
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
|
642
Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java
Normal file
642
Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java
Normal file
@ -0,0 +1,642 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2013 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.corecomponents;
|
||||||
|
|
||||||
|
import com.sun.javafx.application.PlatformImpl;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
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.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.media.Media;
|
||||||
|
import javafx.scene.media.MediaException;
|
||||||
|
import javafx.scene.media.MediaPlayer;
|
||||||
|
import javafx.scene.media.MediaPlayer.Status;
|
||||||
|
import static javafx.scene.media.MediaPlayer.Status.READY;
|
||||||
|
import javafx.scene.media.MediaView;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
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.modules.ModuleInstall;
|
||||||
|
import org.openide.util.Cancellable;
|
||||||
|
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.datamodel.ContentUtils;
|
||||||
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Video viewer part of the Media View layered pane.
|
||||||
|
*/
|
||||||
|
@ServiceProviders(value = {
|
||||||
|
@ServiceProvider(service = FrameCapture.class)
|
||||||
|
})
|
||||||
|
public class FXVideoPanel extends MediaViewVideoPanel {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
|
||||||
|
private boolean fxInited = false;
|
||||||
|
// FX Components
|
||||||
|
private MediaPlayer fxMediaPlayer;
|
||||||
|
private MediaPane mediaPane;
|
||||||
|
// Current media content representations
|
||||||
|
private AbstractFile currentFile;
|
||||||
|
// FX UI Components
|
||||||
|
private JFXPanel videoComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new form MediaViewVideoPanel
|
||||||
|
*/
|
||||||
|
public FXVideoPanel() {
|
||||||
|
org.sleuthkit.autopsy.core.Installer coreInstaller =
|
||||||
|
ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false);
|
||||||
|
if (coreInstaller != null) {
|
||||||
|
fxInited = coreInstaller.isJavaFxInited();
|
||||||
|
}
|
||||||
|
initComponents();
|
||||||
|
customizeComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JPanel getVideoPanel() {
|
||||||
|
return videoPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getVideoComponent() {
|
||||||
|
return videoComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void customizeComponents() {
|
||||||
|
setupFx();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
synchronized void setupVideo(final AbstractFile file, final Dimension dims) {
|
||||||
|
currentFile = file;
|
||||||
|
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
|
||||||
|
if (deleted) {
|
||||||
|
mediaPane.setInfoLabelText("Playback of deleted videos is not supported, use an external player.");
|
||||||
|
videoPanel.removeAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = "";
|
||||||
|
try {
|
||||||
|
path = file.getUniquePath();
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Cannot get unique path of video file");
|
||||||
|
}
|
||||||
|
mediaPane.setInfoLabelText(path);
|
||||||
|
mediaPane.setInfoLabelToolTipText(path);
|
||||||
|
|
||||||
|
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
|
||||||
|
em.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void setupFx() {
|
||||||
|
if(!fxInited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.log(Level.INFO, "In Setup FX");
|
||||||
|
PlatformImpl.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mediaPane = new MediaPane();
|
||||||
|
logger.log(Level.INFO, "Created MediaPane");
|
||||||
|
Scene fxScene = new Scene(mediaPane);
|
||||||
|
videoComponent = new JFXPanel();
|
||||||
|
videoComponent.setScene(fxScene);
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Configure VideoPanel
|
||||||
|
videoPanel.removeAll();
|
||||||
|
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
|
||||||
|
videoPanel.add(videoComponent);
|
||||||
|
videoPanel.setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void reset() {
|
||||||
|
|
||||||
|
PlatformImpl.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (fxMediaPlayer != null) {
|
||||||
|
if (fxMediaPlayer.getStatus() == MediaPlayer.Status.PLAYING ) {
|
||||||
|
fxMediaPlayer.stop();
|
||||||
|
}
|
||||||
|
fxMediaPlayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoComponent != null) {
|
||||||
|
videoComponent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
currentFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.io.File getJFile(AbstractFile file) {
|
||||||
|
// Get the temp folder path of the case
|
||||||
|
String tempPath = Case.getCurrentCase().getTempDirectory();
|
||||||
|
String name = file.getName();
|
||||||
|
int extStart = name.lastIndexOf(".");
|
||||||
|
String ext = "";
|
||||||
|
if (extStart != -1) {
|
||||||
|
ext = name.substring(extStart, name.length()).toLowerCase();
|
||||||
|
}
|
||||||
|
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
|
||||||
|
|
||||||
|
java.io.File tempFile = new java.io.File(tempPath);
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called from within the constructor to initialize the form.
|
||||||
|
* WARNING: Do NOT modify this code. The content of this method is always
|
||||||
|
* regenerated by the Form Editor.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">
|
||||||
|
private void initComponents() {
|
||||||
|
|
||||||
|
videoPanel = new javax.swing.JPanel();
|
||||||
|
|
||||||
|
javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
|
||||||
|
videoPanel.setLayout(videoPanelLayout);
|
||||||
|
videoPanelLayout.setHorizontalGroup(
|
||||||
|
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGap(0, 448, Short.MAX_VALUE)
|
||||||
|
);
|
||||||
|
videoPanelLayout.setVerticalGroup(
|
||||||
|
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGap(0, 248, 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(videoPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
);
|
||||||
|
layout.setVerticalGroup(
|
||||||
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
);
|
||||||
|
}// </editor-fold>
|
||||||
|
|
||||||
|
// Variables declaration - do not modify
|
||||||
|
private javax.swing.JPanel videoPanel;
|
||||||
|
// End of variables declaration
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInited() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Media getMedia() {
|
||||||
|
return new Media(Paths.get(jFile.getAbsolutePath()).toUri().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object doInBackground() throws Exception {
|
||||||
|
success = false;
|
||||||
|
progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() {
|
||||||
|
@Override
|
||||||
|
public boolean cancel() {
|
||||||
|
return ExtractMedia.this.cancel(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mediaPane.setProgressLabelText("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);
|
||||||
|
}
|
||||||
|
logger.log(Level.INFO, "Done buffering: " + jFile.getName());
|
||||||
|
success = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clean up or start the worker threads */
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
super.get(); //block and get all exceptions thrown while doInBackground()
|
||||||
|
} catch (CancellationException ex) {
|
||||||
|
logger.log(Level.INFO, "Media buffering was canceled.");
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.INFO, "Media buffering was interrupted.");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex);
|
||||||
|
} finally {
|
||||||
|
progress.finish();
|
||||||
|
if (!this.isCancelled()) {
|
||||||
|
logger.log(Level.INFO, "ExtractMedia in done: " + jFile.getName());
|
||||||
|
try {
|
||||||
|
PlatformImpl.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
fxMediaPlayer = new MediaPlayer(getMedia());
|
||||||
|
logger.log(Level.INFO, "Fx Media Player null? " + (fxMediaPlayer == null));
|
||||||
|
logger.log(Level.INFO, "Media Tools null? " + (mediaPane == null));
|
||||||
|
mediaPane.setMediaPlayer(fxMediaPlayer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(MediaException e) {
|
||||||
|
logger.log(Level.WARNING, "something went wrong with javafx", e);
|
||||||
|
reset();
|
||||||
|
mediaPane.setInfoLabelText(e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MediaPane extends BorderPane {
|
||||||
|
private MediaPlayer mediaPlayer;
|
||||||
|
private MediaView mediaView;
|
||||||
|
private Duration duration;
|
||||||
|
private HBox mediaTools;
|
||||||
|
private HBox mediaViewPane;
|
||||||
|
private Slider progressSlider;
|
||||||
|
private Button pauseButton;
|
||||||
|
private Label progressLabel;
|
||||||
|
private Label infoLabel;
|
||||||
|
private int totalHours;
|
||||||
|
private int totalMinutes;
|
||||||
|
private int totalSeconds;
|
||||||
|
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d ";
|
||||||
|
|
||||||
|
public MediaPane() {
|
||||||
|
// Video Display
|
||||||
|
mediaViewPane = new HBox();
|
||||||
|
mediaViewPane.setStyle("-fx-background-color: black");
|
||||||
|
mediaViewPane.setAlignment(Pos.CENTER);
|
||||||
|
mediaView = new MediaView();
|
||||||
|
mediaViewPane.getChildren().add(mediaView);
|
||||||
|
setAlignment(mediaViewPane, Pos.CENTER);
|
||||||
|
setCenter(mediaViewPane);
|
||||||
|
|
||||||
|
// Media Controls
|
||||||
|
VBox controlPanel = new VBox();
|
||||||
|
mediaTools = new HBox();
|
||||||
|
mediaTools.setAlignment(Pos.CENTER);
|
||||||
|
mediaTools.setPadding(new Insets(5, 10, 5, 10));
|
||||||
|
|
||||||
|
pauseButton = new Button("►");
|
||||||
|
mediaTools.getChildren().add(pauseButton);
|
||||||
|
mediaTools.getChildren().add(new Label(" "));
|
||||||
|
progressSlider = new Slider();
|
||||||
|
HBox.setHgrow(progressSlider,Priority.ALWAYS);
|
||||||
|
progressSlider.setMinWidth(50);
|
||||||
|
progressSlider.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
mediaTools.getChildren().add(progressSlider);
|
||||||
|
progressLabel = new Label();
|
||||||
|
progressLabel.setPrefWidth(130);
|
||||||
|
progressLabel.setMinWidth(50);
|
||||||
|
mediaTools.getChildren().add(progressLabel);
|
||||||
|
|
||||||
|
controlPanel.getChildren().add(mediaTools);
|
||||||
|
controlPanel.setStyle("-fx-background-color: white");
|
||||||
|
infoLabel = new Label("");
|
||||||
|
controlPanel.getChildren().add(infoLabel);
|
||||||
|
setBottom(controlPanel);
|
||||||
|
setProgressActionListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfoLabelText(final String text) {
|
||||||
|
PlatformImpl.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
infoLabel.setText(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediaPlayer(MediaPlayer mp) {
|
||||||
|
pauseButton.setDisable(true);
|
||||||
|
mediaPlayer = mp;
|
||||||
|
mediaView.setMediaPlayer(mp);
|
||||||
|
pauseButton.setDisable(false);
|
||||||
|
|
||||||
|
setMediaActionListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMediaActionListeners() {
|
||||||
|
mediaPlayer.setOnReady(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
duration = mediaPlayer.getMedia().getDuration();
|
||||||
|
long durationInMillis = (long) fxMediaPlayer.getMedia().getDuration().toMillis();
|
||||||
|
|
||||||
|
// pick out the total hours, minutes, seconds
|
||||||
|
long durationSeconds = (int) durationInMillis / 1000;
|
||||||
|
totalHours = (int) durationSeconds / 3600;
|
||||||
|
durationSeconds -= totalHours * 3600;
|
||||||
|
totalMinutes = (int) durationSeconds / 60;
|
||||||
|
durationSeconds -= totalMinutes * 60;
|
||||||
|
totalSeconds = (int) durationSeconds;
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaPlayer.setOnEndOfMedia(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Duration beginning = mediaPlayer.getStartTime();
|
||||||
|
mediaPlayer.stop();
|
||||||
|
mediaPlayer.pause();
|
||||||
|
pauseButton.setText("►");
|
||||||
|
updateSlider(beginning);
|
||||||
|
updateTime(beginning);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaPlayer.currentTimeProperty().addListener(new ChangeListener<Duration>() {
|
||||||
|
@Override
|
||||||
|
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
|
||||||
|
updateSlider(newValue);
|
||||||
|
updateTime(newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProgressActionListeners() {
|
||||||
|
pauseButton.setOnAction(new EventHandler<ActionEvent>() {
|
||||||
|
@Override
|
||||||
|
public void handle(ActionEvent e) {
|
||||||
|
Status status = mediaPlayer.getStatus();
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
// If playing, pause
|
||||||
|
case PLAYING:
|
||||||
|
pauseButton.setText("►");
|
||||||
|
mediaPlayer.pause();
|
||||||
|
break;
|
||||||
|
// If ready, paused or stopped, continue playing
|
||||||
|
case READY:
|
||||||
|
case PAUSED:
|
||||||
|
case STOPPED:
|
||||||
|
pauseButton.setText("||");
|
||||||
|
mediaPlayer.play();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
progressSlider.valueProperty().addListener(new InvalidationListener() {
|
||||||
|
@Override
|
||||||
|
public void invalidated(Observable o) {
|
||||||
|
if (progressSlider.isValueChanging()) {
|
||||||
|
mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProgress() {
|
||||||
|
Duration currentTime = mediaPlayer.getCurrentTime();
|
||||||
|
updateSlider(currentTime);
|
||||||
|
updateTime(currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSlider(Duration currentTime) {
|
||||||
|
if (progressSlider != null) {
|
||||||
|
progressSlider.setDisable(duration.isUnknown());
|
||||||
|
if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO)
|
||||||
|
&& !progressSlider.isValueChanging()) {
|
||||||
|
progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTime(Duration currentTime) {
|
||||||
|
long millisElapsed = (long) currentTime.toMillis();
|
||||||
|
|
||||||
|
long elapsedHours, elapsedMinutes, elapsedSeconds;
|
||||||
|
// pick out the elapsed hours, minutes, seconds
|
||||||
|
long secondsElapsed = millisElapsed / 1000;
|
||||||
|
elapsedHours = (int) secondsElapsed / 3600;
|
||||||
|
secondsElapsed -= elapsedHours * 3600;
|
||||||
|
elapsedMinutes = (int) secondsElapsed / 60;
|
||||||
|
secondsElapsed -= elapsedMinutes * 60;
|
||||||
|
elapsedSeconds = (int) secondsElapsed;
|
||||||
|
|
||||||
|
String durationStr = String.format(durationFormat,
|
||||||
|
elapsedHours, elapsedMinutes, elapsedSeconds,
|
||||||
|
totalHours, totalMinutes, totalSeconds);
|
||||||
|
progressLabel.setText(durationStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProgressLabelText(final String text) {
|
||||||
|
PlatformImpl.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
progressLabel.setText(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setInfoLabelToolTipText(final String text) {
|
||||||
|
PlatformImpl.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
infoLabel.setTooltip(new Tooltip(text));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
* @return a List of VideoFrames representing the captured frames.
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
// }
|
||||||
|
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) {
|
||||||
|
// PlatformImpl.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;
|
||||||
|
//
|
||||||
|
// // PlatformImpl.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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
770
Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java
Normal file
770
Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java
Normal file
@ -0,0 +1,770 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2013 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.corecomponents;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
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;
|
||||||
|
import org.gstreamer.State;
|
||||||
|
import org.gstreamer.StateChangeReturn;
|
||||||
|
import org.gstreamer.elements.PlayBin2;
|
||||||
|
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.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.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 Logger logger = Logger.getLogger(GstVideoPanel.class.getName());
|
||||||
|
private boolean gstInited;
|
||||||
|
private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
|
||||||
|
private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000;
|
||||||
|
private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file.";
|
||||||
|
//playback
|
||||||
|
private long durationMillis = 0;
|
||||||
|
private VideoProgressWorker videoProgressWorker;
|
||||||
|
private int totalHours, totalMinutes, totalSeconds;
|
||||||
|
private volatile PlayBin2 gstPlaybin2;
|
||||||
|
private VideoComponent gstVideoComponent;
|
||||||
|
private boolean autoTracking = false; // true if the slider is moving automatically
|
||||||
|
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
|
||||||
|
private AbstractFile currentFile;
|
||||||
|
private Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new form MediaViewVideoPanel
|
||||||
|
*/
|
||||||
|
public GstVideoPanel() {
|
||||||
|
initComponents();
|
||||||
|
customizeComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getPauseButton() {
|
||||||
|
return pauseButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JLabel getProgressLabel() {
|
||||||
|
return progressLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSlider getProgressSlider() {
|
||||||
|
return progressSlider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JPanel getVideoPanel() {
|
||||||
|
return videoPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoComponent getVideoComponent() {
|
||||||
|
return gstVideoComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInited() {
|
||||||
|
return gstInited;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void customizeComponents() {
|
||||||
|
if (!initGst()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
|
||||||
|
progressSlider.setValue(0);
|
||||||
|
|
||||||
|
progressSlider.addChangeListener(new ChangeListener() {
|
||||||
|
/**
|
||||||
|
* 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.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gstPlaybin2.setState(orig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean initGst() {
|
||||||
|
try {
|
||||||
|
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing");
|
||||||
|
Gst.init();
|
||||||
|
gstInited = true;
|
||||||
|
} catch (GstException e) {
|
||||||
|
gstInited = false;
|
||||||
|
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e);
|
||||||
|
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. "
|
||||||
|
+ " Video and audio viewing will be disabled. ",
|
||||||
|
e.getMessage());
|
||||||
|
return false;
|
||||||
|
} catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
|
||||||
|
gstInited = false;
|
||||||
|
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e);
|
||||||
|
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. "
|
||||||
|
+ " Video and audio viewing will be disabled. ",
|
||||||
|
e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setupVideo(final AbstractFile file, final Dimension dims) {
|
||||||
|
infoLabel.setText("");
|
||||||
|
currentFile = file;
|
||||||
|
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
|
||||||
|
if (deleted) {
|
||||||
|
infoLabel.setText("Playback of deleted videos is not supported, use an external player.");
|
||||||
|
videoPanel.removeAll();
|
||||||
|
pauseButton.setEnabled(false);
|
||||||
|
progressSlider.setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = "";
|
||||||
|
try {
|
||||||
|
path = file.getUniquePath();
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Cannot get unique path of video file");
|
||||||
|
}
|
||||||
|
infoLabel.setText(path);
|
||||||
|
infoLabel.setToolTipText(path);
|
||||||
|
pauseButton.setEnabled(true);
|
||||||
|
progressSlider.setEnabled(true);
|
||||||
|
|
||||||
|
java.io.File ioFile = getJFile(file);
|
||||||
|
|
||||||
|
gstVideoComponent = new VideoComponent();
|
||||||
|
synchronized (playbinLock) {
|
||||||
|
if (gstPlaybin2 != null) {
|
||||||
|
gstPlaybin2.dispose();
|
||||||
|
}
|
||||||
|
gstPlaybin2 = new PlayBin2("VideoPlayer");
|
||||||
|
gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
|
||||||
|
|
||||||
|
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.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void reset() {
|
||||||
|
|
||||||
|
// reset the progress label text on the event dispatch thread
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
progressLabel.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isInited()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (playbinLock) {
|
||||||
|
if (gstPlaybin2 != null) {
|
||||||
|
if (gstPlaybin2.isPlaying()) {
|
||||||
|
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gstPlaybin2.getState().equals(State.NULL)) {
|
||||||
|
gstPlaybin2.dispose();
|
||||||
|
}
|
||||||
|
gstPlaybin2 = null;
|
||||||
|
}
|
||||||
|
gstVideoComponent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get rid of any existing videoProgressWorker thread
|
||||||
|
if (videoProgressWorker != null) {
|
||||||
|
videoProgressWorker.cancel(true);
|
||||||
|
videoProgressWorker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.io.File getJFile(AbstractFile file) {
|
||||||
|
// Get the temp folder path of the case
|
||||||
|
String tempPath = Case.getCurrentCase().getTempDirectory();
|
||||||
|
String name = file.getName();
|
||||||
|
int extStart = name.lastIndexOf(".");
|
||||||
|
String ext = "";
|
||||||
|
if (extStart != -1) {
|
||||||
|
ext = name.substring(extStart, name.length()).toLowerCase();
|
||||||
|
}
|
||||||
|
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
|
||||||
|
|
||||||
|
java.io.File tempFile = new java.io.File(tempPath);
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param file a video file from which to capture frames
|
||||||
|
* @param 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.
|
||||||
|
* @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("Cannot capture frames from this file (" + file.getName() + ").");
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up a PlayBin2 object
|
||||||
|
RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener);
|
||||||
|
PlayBin2 playbin = new PlayBin2("VideoFrameCapture");
|
||||||
|
playbin.setInputFile(file);
|
||||||
|
playbin.setVideoSink(videoSink);
|
||||||
|
|
||||||
|
// this is necessary to get a valid duration value
|
||||||
|
StateChangeReturn ret = playbin.play();
|
||||||
|
if (ret == StateChangeReturn.FAILURE) {
|
||||||
|
// add this file to the set of known bad ones
|
||||||
|
badVideoFiles.add(file.getName());
|
||||||
|
throw new Exception("Problem with video file; problem when attempting to play while obtaining duration.");
|
||||||
|
}
|
||||||
|
ret = playbin.pause();
|
||||||
|
if (ret == StateChangeReturn.FAILURE) {
|
||||||
|
// add this file to the set of known bad ones
|
||||||
|
badVideoFiles.add(file.getName());
|
||||||
|
throw new Exception("Problem with video file; problem when attempting to pause while obtaining duration.");
|
||||||
|
}
|
||||||
|
playbin.getState();
|
||||||
|
|
||||||
|
// get the duration of the video
|
||||||
|
TimeUnit unit = TimeUnit.MILLISECONDS;
|
||||||
|
long myDurationMillis = playbin.queryDuration(unit);
|
||||||
|
if (myDurationMillis <= 0) {
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the number of frames to capture
|
||||||
|
int numFramesToGet = numFrames;
|
||||||
|
long frameInterval = myDurationMillis / numFrames;
|
||||||
|
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
|
||||||
|
numFramesToGet = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each timeStamp, grap a frame
|
||||||
|
for (int i = 0; i < numFramesToGet; ++i) {
|
||||||
|
long timeStamp = i * frameInterval;
|
||||||
|
|
||||||
|
ret = playbin.pause();
|
||||||
|
if (ret == StateChangeReturn.FAILURE) {
|
||||||
|
// add this file to the set of known bad ones
|
||||||
|
badVideoFiles.add(file.getName());
|
||||||
|
throw new Exception("Problem with video file; problem when attempting to pause while capturing a frame.");
|
||||||
|
}
|
||||||
|
playbin.getState();
|
||||||
|
|
||||||
|
//System.out.println("Seeking to " + timeStamp + "milliseconds.");
|
||||||
|
if (!playbin.seek(timeStamp, unit)) {
|
||||||
|
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = playbin.play();
|
||||||
|
if (ret == StateChangeReturn.FAILURE) {
|
||||||
|
// add this file to the set of known bad ones
|
||||||
|
badVideoFiles.add(file.getName());
|
||||||
|
throw new Exception("Problem with video file; problem when attempting to play while capturing a frame.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for FrameCaptureRGBListener to finish
|
||||||
|
synchronized(lock) {
|
||||||
|
try {
|
||||||
|
lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Image image = rgbListener.getImage();
|
||||||
|
|
||||||
|
ret = playbin.stop();
|
||||||
|
if (ret == StateChangeReturn.FAILURE) {
|
||||||
|
// add this file to the set of known bad ones
|
||||||
|
badVideoFiles.add(file.getName());
|
||||||
|
throw new Exception("Problem with video file; problem when attempting to stop while capturing a frame.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image == null) {
|
||||||
|
logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName());
|
||||||
|
badVideoFiles.add(file.getName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.add(new VideoFrame(image, timeStamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FrameCaptureRGBListener implements RGBDataSink.Listener {
|
||||||
|
|
||||||
|
public FrameCaptureRGBListener(Object waiter) {
|
||||||
|
this.waiter = waiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage bi;
|
||||||
|
private final Object waiter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
|
||||||
|
synchronized (waiter) {
|
||||||
|
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
|
||||||
|
waiter.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image getImage() {
|
||||||
|
synchronized (waiter) {
|
||||||
|
Image image = bi;
|
||||||
|
bi = null;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* regenerated by the Form Editor.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">
|
||||||
|
private void initComponents() {
|
||||||
|
|
||||||
|
videoPanel = new javax.swing.JPanel();
|
||||||
|
controlPanel = new javax.swing.JPanel();
|
||||||
|
pauseButton = new javax.swing.JButton();
|
||||||
|
progressSlider = new javax.swing.JSlider();
|
||||||
|
progressLabel = new javax.swing.JLabel();
|
||||||
|
infoLabel = new javax.swing.JLabel();
|
||||||
|
|
||||||
|
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.setVerticalGroup(
|
||||||
|
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGap(0, 188, Short.MAX_VALUE)
|
||||||
|
);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
|
||||||
|
pauseButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
pauseButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N
|
||||||
|
|
||||||
|
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
|
||||||
|
controlPanel.setLayout(controlPanelLayout);
|
||||||
|
controlPanelLayout.setHorizontalGroup(
|
||||||
|
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||||
|
.addComponent(pauseButton)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addComponent(progressLabel)
|
||||||
|
.addContainerGap())
|
||||||
|
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||||
|
.addComponent(infoLabel)
|
||||||
|
.addGap(0, 0, Short.MAX_VALUE))
|
||||||
|
);
|
||||||
|
controlPanelLayout.setVerticalGroup(
|
||||||
|
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||||
|
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addComponent(pauseButton)
|
||||||
|
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addComponent(progressLabel, javax.swing.GroupLayout.Alignment.TRAILING))
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addComponent(infoLabel))
|
||||||
|
);
|
||||||
|
|
||||||
|
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||||
|
this.setLayout(layout);
|
||||||
|
layout.setHorizontalGroup(
|
||||||
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addComponent(videoPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
.addComponent(controlPanel, 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)
|
||||||
|
.addContainerGap())
|
||||||
|
);
|
||||||
|
}// </editor-fold>
|
||||||
|
|
||||||
|
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
synchronized (playbinLock) {
|
||||||
|
if (gstPlaybin2 == null) {
|
||||||
|
infoLabel.setText("Error: Playbin is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
State state = gstPlaybin2.getState();
|
||||||
|
if (state.equals(State.PLAYING)) {
|
||||||
|
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pauseButton.setText("►");
|
||||||
|
// Is this call necessary considering we just called gstPlaybin2.pause()?
|
||||||
|
if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (state.equals(State.PAUSED)) {
|
||||||
|
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pauseButton.setText("||");
|
||||||
|
// Is this call necessary considering we just called gstPlaybin2.play()?
|
||||||
|
if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (state.equals(State.READY)) {
|
||||||
|
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
|
||||||
|
em.execute();
|
||||||
|
em.getExtractedBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Variables declaration - do not modify
|
||||||
|
private javax.swing.JPanel controlPanel;
|
||||||
|
private javax.swing.JLabel infoLabel;
|
||||||
|
private javax.swing.JButton pauseButton;
|
||||||
|
private javax.swing.JLabel progressLabel;
|
||||||
|
private javax.swing.JSlider progressSlider;
|
||||||
|
private javax.swing.JPanel videoPanel;
|
||||||
|
// End of variables declaration
|
||||||
|
|
||||||
|
private class VideoProgressWorker extends SwingWorker<Object, Object> {
|
||||||
|
|
||||||
|
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d ";
|
||||||
|
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) {
|
||||||
|
return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetVideo() throws Exception {
|
||||||
|
synchronized (playbinLock) {
|
||||||
|
if (gstPlaybin2 != null) {
|
||||||
|
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
}
|
||||||
|
// ready to be played again
|
||||||
|
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
}
|
||||||
|
gstPlaybin2.getState(); //NEW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pauseButton.setText("►");
|
||||||
|
progressSlider.setValue(0);
|
||||||
|
|
||||||
|
String durationStr = String.format(durationFormat, 0, 0, 0,
|
||||||
|
totalHours, totalMinutes, totalSeconds);
|
||||||
|
progressLabel.setText(durationStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
private boolean hasNotEnded() {
|
||||||
|
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object doInBackground() throws Exception {
|
||||||
|
|
||||||
|
// enable the slider
|
||||||
|
progressSlider.setEnabled(true);
|
||||||
|
|
||||||
|
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
|
||||||
|
ClockTime pos = null;
|
||||||
|
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
|
||||||
|
|
||||||
|
synchronized (playbinLock) {
|
||||||
|
pos = gstPlaybin2.queryPosition();
|
||||||
|
}
|
||||||
|
millisElapsed = pos.toMillis();
|
||||||
|
|
||||||
|
// pick out the elapsed hours, minutes, seconds
|
||||||
|
long secondsElapsed = millisElapsed / 1000;
|
||||||
|
elapsedHours = (int) secondsElapsed / 3600;
|
||||||
|
secondsElapsed -= elapsedHours * 3600;
|
||||||
|
elapsedMinutes = (int) secondsElapsed / 60;
|
||||||
|
secondsElapsed -= elapsedMinutes * 60;
|
||||||
|
elapsedSeconds = (int) secondsElapsed;
|
||||||
|
|
||||||
|
String durationStr = String.format(durationFormat,
|
||||||
|
elapsedHours, elapsedMinutes, elapsedSeconds,
|
||||||
|
totalHours, totalMinutes, totalSeconds);
|
||||||
|
|
||||||
|
progressLabel.setText(durationStr);
|
||||||
|
autoTracking = true;
|
||||||
|
progressSlider.setValue((int) millisElapsed);
|
||||||
|
autoTracking = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(INTER_FRAME_PERIOD_MS);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable the slider
|
||||||
|
progressSlider.setEnabled(false);
|
||||||
|
|
||||||
|
resetVideo();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} //end class progress worker
|
||||||
|
|
||||||
|
/* Thread that extracts and plays a file */
|
||||||
|
private class ExtractMedia extends SwingWorker<Object, Void> {
|
||||||
|
|
||||||
|
private ProgressHandle progress;
|
||||||
|
boolean success = false;
|
||||||
|
private AbstractFile sFile;
|
||||||
|
private java.io.File jFile;
|
||||||
|
private String duration;
|
||||||
|
private String position;
|
||||||
|
private long extractedBytes;
|
||||||
|
|
||||||
|
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
|
||||||
|
this.sFile = sFile;
|
||||||
|
this.jFile = jFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExtractedBytes() {
|
||||||
|
return extractedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object doInBackground() throws Exception {
|
||||||
|
success = false;
|
||||||
|
progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() {
|
||||||
|
@Override
|
||||||
|
public boolean cancel() {
|
||||||
|
return ExtractMedia.this.cancel(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
progressLabel.setText("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);
|
||||||
|
}
|
||||||
|
success = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clean up or start the worker threads */
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
super.get(); //block and get all exceptions thrown while doInBackground()
|
||||||
|
} catch (CancellationException ex) {
|
||||||
|
logger.log(Level.INFO, "Media buffering was canceled.");
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.INFO, "Media buffering was interrupted.");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex);
|
||||||
|
} finally {
|
||||||
|
progress.finish();
|
||||||
|
if (!this.isCancelled()) {
|
||||||
|
playMedia();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void playMedia() {
|
||||||
|
if (jFile == null || !jFile.exists()) {
|
||||||
|
progressLabel.setText("Error buffering file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClockTime dur = null;
|
||||||
|
synchronized (playbinLock) {
|
||||||
|
// must play, then pause and get state to get duration.
|
||||||
|
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gstPlaybin2.getState();
|
||||||
|
dur = gstPlaybin2.queryDuration();
|
||||||
|
}
|
||||||
|
duration = dur.toString();
|
||||||
|
durationMillis = dur.toMillis();
|
||||||
|
|
||||||
|
// pick out the total hours, minutes, seconds
|
||||||
|
long durationSeconds = (int) durationMillis / 1000;
|
||||||
|
totalHours = (int) durationSeconds / 3600;
|
||||||
|
durationSeconds -= totalHours * 3600;
|
||||||
|
totalMinutes = (int) durationSeconds / 60;
|
||||||
|
durationSeconds -= totalMinutes * 60;
|
||||||
|
totalSeconds = (int) durationSeconds;
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
progressSlider.setMaximum((int) durationMillis);
|
||||||
|
progressSlider.setMinimum(0);
|
||||||
|
|
||||||
|
synchronized (playbinLock) {
|
||||||
|
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
||||||
|
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
|
||||||
|
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pauseButton.setText("||");
|
||||||
|
videoProgressWorker = new VideoProgressWorker();
|
||||||
|
videoProgressWorker.execute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,113 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
|
|
||||||
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
|
||||||
<AuxValues>
|
|
||||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
|
||||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
|
||||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
|
||||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
|
||||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
|
|
||||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
|
||||||
<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"/>
|
|
||||||
</AuxValues>
|
|
||||||
|
|
||||||
<Layout>
|
|
||||||
<DimensionLayout dim="0">
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
|
||||||
<Component id="videoPanel" alignment="1" max="32767" attributes="0"/>
|
|
||||||
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
</DimensionLayout>
|
|
||||||
<DimensionLayout dim="1">
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
|
||||||
<Group type="102" attributes="0">
|
|
||||||
<Component id="videoPanel" max="32767" attributes="0"/>
|
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
|
||||||
<Component id="controlPanel" min="-2" max="-2" attributes="0"/>
|
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
</DimensionLayout>
|
|
||||||
</Layout>
|
|
||||||
<SubComponents>
|
|
||||||
<Container class="javax.swing.JPanel" name="videoPanel">
|
|
||||||
|
|
||||||
<Layout>
|
|
||||||
<DimensionLayout dim="0">
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
|
||||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
</DimensionLayout>
|
|
||||||
<DimensionLayout dim="1">
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
|
||||||
<EmptySpace min="0" pref="188" max="32767" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
</DimensionLayout>
|
|
||||||
</Layout>
|
|
||||||
</Container>
|
|
||||||
<Container class="javax.swing.JPanel" name="controlPanel">
|
|
||||||
|
|
||||||
<Layout>
|
|
||||||
<DimensionLayout dim="0">
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
|
||||||
<Group type="102" attributes="0">
|
|
||||||
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
|
|
||||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
|
||||||
<Component id="progressSlider" pref="357" max="32767" attributes="0"/>
|
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
|
||||||
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
|
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
<Group type="102" attributes="0">
|
|
||||||
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
|
|
||||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
</DimensionLayout>
|
|
||||||
<DimensionLayout dim="1">
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
|
||||||
<Group type="102" attributes="0">
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
|
||||||
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
|
|
||||||
<Component id="progressSlider" min="-2" max="-2" attributes="0"/>
|
|
||||||
<Component id="progressLabel" alignment="1" min="-2" max="-2" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
|
||||||
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
</DimensionLayout>
|
|
||||||
</Layout>
|
|
||||||
<SubComponents>
|
|
||||||
<Component class="javax.swing.JButton" name="pauseButton">
|
|
||||||
<Properties>
|
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.pauseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
|
||||||
</Property>
|
|
||||||
</Properties>
|
|
||||||
<Events>
|
|
||||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pauseButtonActionPerformed"/>
|
|
||||||
</Events>
|
|
||||||
</Component>
|
|
||||||
<Component class="javax.swing.JSlider" name="progressSlider">
|
|
||||||
</Component>
|
|
||||||
<Component class="javax.swing.JLabel" name="progressLabel">
|
|
||||||
<Properties>
|
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
|
||||||
</Property>
|
|
||||||
</Properties>
|
|
||||||
</Component>
|
|
||||||
<Component class="javax.swing.JLabel" name="infoLabel">
|
|
||||||
<Properties>
|
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
|
||||||
</Property>
|
|
||||||
</Properties>
|
|
||||||
</Component>
|
|
||||||
</SubComponents>
|
|
||||||
</Container>
|
|
||||||
</SubComponents>
|
|
||||||
</Form>
|
|
@ -19,753 +19,100 @@
|
|||||||
package org.sleuthkit.autopsy.corecomponents;
|
package org.sleuthkit.autopsy.corecomponents;
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Image;
|
import java.util.Arrays;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.IntBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.swing.BoxLayout;
|
import java.util.logging.Logger;
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
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;
|
|
||||||
import org.gstreamer.State;
|
|
||||||
import org.gstreamer.StateChangeReturn;
|
|
||||||
import org.gstreamer.elements.PlayBin2;
|
|
||||||
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.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.datamodel.ContentUtils;
|
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
|
||||||
import org.sleuthkit.datamodel.TskData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Video viewer part of the Media View layered pane
|
* Video viewer part of the Media View layered pane.
|
||||||
|
* Uses different engines depending on platform.
|
||||||
*/
|
*/
|
||||||
@ServiceProviders(value = {
|
public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture {
|
||||||
@ServiceProvider(service = FrameCapture.class)
|
|
||||||
})
|
|
||||||
public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
|
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
|
||||||
private boolean gstInited;
|
|
||||||
private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
|
// 64 bit architectures
|
||||||
private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000;
|
private static final String[] ARCH64 = new String[]{"amd64", "x86_64"};
|
||||||
private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file.";
|
|
||||||
//playback
|
// 32 bit architectures
|
||||||
private long durationMillis = 0;
|
private static final String[] ARCH32 = new String[]{"x86"};
|
||||||
private VideoProgressWorker videoProgressWorker;
|
|
||||||
private int totalHours, totalMinutes, totalSeconds;
|
// A Gstreamer implementation of MediaViewVideoPanel
|
||||||
private volatile PlayBin2 gstPlaybin2;
|
private static GstVideoPanel gstVideoPanel = null;
|
||||||
private VideoComponent gstVideoComponent;
|
|
||||||
private boolean autoTracking = false; // true if the slider is moving automatically
|
// A JavaFX implmentation of MediaViewVideoPanel
|
||||||
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
|
private static FXVideoPanel fxVideoPanel = null;
|
||||||
private AbstractFile currentFile;
|
|
||||||
private Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
|
/**
|
||||||
|
* Factory Method to create a MediaViewVideoPanel.
|
||||||
|
*
|
||||||
|
* Implementation is dependent on the architecture of the JVM.
|
||||||
|
*
|
||||||
|
* @return a MediaViewVideoPanel instance.
|
||||||
|
*/
|
||||||
|
public static MediaViewVideoPanel createVideoPanel() {
|
||||||
|
if (is64BitJVM()) {
|
||||||
|
logger.log(Level.INFO, "64 bit JVM detected. Creating JavaFX Video Player.");
|
||||||
|
return getFXImpl();
|
||||||
|
} else {
|
||||||
|
logger.log(Level.INFO, "32 bit JVM detected. Creating GStreamer Video Player.");
|
||||||
|
return getGstImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the JVM architecture 64 bit?
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static boolean is64BitJVM() {
|
||||||
|
String arch = System.getProperty("os.arch");
|
||||||
|
return Arrays.asList(ARCH64).contains(arch);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form MediaViewVideoPanel
|
* Get a GStreamer video player implementation.
|
||||||
|
*
|
||||||
|
* @return a GstVideoPanel
|
||||||
*/
|
*/
|
||||||
public MediaViewVideoPanel() {
|
private static MediaViewVideoPanel getGstImpl() {
|
||||||
initComponents();
|
if (gstVideoPanel == null) {
|
||||||
customizeComponents();
|
gstVideoPanel = new GstVideoPanel();
|
||||||
}
|
|
||||||
|
|
||||||
public JButton getPauseButton() {
|
|
||||||
return pauseButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JLabel getProgressLabel() {
|
|
||||||
return progressLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSlider getProgressSlider() {
|
|
||||||
return progressSlider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JPanel getVideoPanel() {
|
|
||||||
return videoPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VideoComponent getVideoComponent() {
|
|
||||||
return gstVideoComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInited() {
|
|
||||||
return gstInited;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void customizeComponents() {
|
|
||||||
if (!initGst()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return gstVideoPanel;
|
||||||
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
|
|
||||||
progressSlider.setValue(0);
|
|
||||||
|
|
||||||
progressSlider.addChangeListener(new ChangeListener() {
|
|
||||||
/**
|
|
||||||
* 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.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gstPlaybin2.setState(orig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean initGst() {
|
/**
|
||||||
try {
|
* Get a JavaFX video player implementation.
|
||||||
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing");
|
*
|
||||||
Gst.init();
|
* @return a FXVideoPanel
|
||||||
gstInited = true;
|
*/
|
||||||
} catch (GstException e) {
|
private static MediaViewVideoPanel getFXImpl() {
|
||||||
gstInited = false;
|
if (fxVideoPanel == null) {
|
||||||
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e);
|
fxVideoPanel = new FXVideoPanel();
|
||||||
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. "
|
|
||||||
+ " Video and audio viewing will be disabled. ",
|
|
||||||
e.getMessage());
|
|
||||||
return false;
|
|
||||||
} catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
|
|
||||||
gstInited = false;
|
|
||||||
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e);
|
|
||||||
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. "
|
|
||||||
+ " Video and audio viewing will be disabled. ",
|
|
||||||
e.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return fxVideoPanel;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has this MediaViewVideoPanel been initialized correctly?
|
||||||
|
*
|
||||||
|
* @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.
|
* Initialize all the necessary vars to play a video/audio file.
|
||||||
*
|
*
|
||||||
* @param file video file to play
|
* @param file video file to play
|
||||||
* @param dims dimension of the parent window
|
* @param dims dimension of the parent window
|
||||||
*/
|
*/
|
||||||
void setupVideo(final AbstractFile file, final Dimension dims) {
|
abstract void setupVideo(final AbstractFile file, final Dimension dims);
|
||||||
infoLabel.setText("");
|
|
||||||
currentFile = file;
|
|
||||||
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
|
|
||||||
if (deleted) {
|
|
||||||
infoLabel.setText("Playback of deleted videos is not supported, use an external player.");
|
|
||||||
videoPanel.removeAll();
|
|
||||||
pauseButton.setEnabled(false);
|
|
||||||
progressSlider.setEnabled(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String path = "";
|
|
||||||
try {
|
|
||||||
path = file.getUniquePath();
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Cannot get unique path of video file");
|
|
||||||
}
|
|
||||||
infoLabel.setText(path);
|
|
||||||
infoLabel.setToolTipText(path);
|
|
||||||
pauseButton.setEnabled(true);
|
|
||||||
progressSlider.setEnabled(true);
|
|
||||||
|
|
||||||
java.io.File ioFile = getJFile(file);
|
|
||||||
|
|
||||||
gstVideoComponent = new VideoComponent();
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
if (gstPlaybin2 != null) {
|
|
||||||
gstPlaybin2.dispose();
|
|
||||||
}
|
|
||||||
gstPlaybin2 = new PlayBin2("VideoPlayer");
|
|
||||||
gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
|
|
||||||
|
|
||||||
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.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
|
|
||||||
// reset the progress label text on the event dispatch thread
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
progressLabel.setText("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isInited()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
if (gstPlaybin2 != null) {
|
|
||||||
if (gstPlaybin2.isPlaying()) {
|
|
||||||
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gstPlaybin2.getState().equals(State.NULL)) {
|
|
||||||
gstPlaybin2.dispose();
|
|
||||||
}
|
|
||||||
gstPlaybin2 = null;
|
|
||||||
}
|
|
||||||
gstVideoComponent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get rid of any existing videoProgressWorker thread
|
|
||||||
if (videoProgressWorker != null) {
|
|
||||||
videoProgressWorker.cancel(true);
|
|
||||||
videoProgressWorker = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFile = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private java.io.File getJFile(AbstractFile file) {
|
|
||||||
// Get the temp folder path of the case
|
|
||||||
String tempPath = Case.getCurrentCase().getTempDirectory();
|
|
||||||
String name = file.getName();
|
|
||||||
int extStart = name.lastIndexOf(".");
|
|
||||||
String ext = "";
|
|
||||||
if (extStart != -1) {
|
|
||||||
ext = name.substring(extStart, name.length()).toLowerCase();
|
|
||||||
}
|
|
||||||
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
|
|
||||||
|
|
||||||
java.io.File tempFile = new java.io.File(tempPath);
|
|
||||||
return tempFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param file a video file from which to capture frames
|
|
||||||
* @param 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.
|
|
||||||
* @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("Cannot capture frames from this file (" + file.getName() + ").");
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up a PlayBin2 object
|
|
||||||
RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener);
|
|
||||||
PlayBin2 playbin = new PlayBin2("VideoFrameCapture");
|
|
||||||
playbin.setInputFile(file);
|
|
||||||
playbin.setVideoSink(videoSink);
|
|
||||||
|
|
||||||
// this is necessary to get a valid duration value
|
|
||||||
StateChangeReturn ret = playbin.play();
|
|
||||||
if (ret == StateChangeReturn.FAILURE) {
|
|
||||||
// add this file to the set of known bad ones
|
|
||||||
badVideoFiles.add(file.getName());
|
|
||||||
throw new Exception("Problem with video file; problem when attempting to play while obtaining duration.");
|
|
||||||
}
|
|
||||||
ret = playbin.pause();
|
|
||||||
if (ret == StateChangeReturn.FAILURE) {
|
|
||||||
// add this file to the set of known bad ones
|
|
||||||
badVideoFiles.add(file.getName());
|
|
||||||
throw new Exception("Problem with video file; problem when attempting to pause while obtaining duration.");
|
|
||||||
}
|
|
||||||
playbin.getState();
|
|
||||||
|
|
||||||
// get the duration of the video
|
|
||||||
TimeUnit unit = TimeUnit.MILLISECONDS;
|
|
||||||
long myDurationMillis = playbin.queryDuration(unit);
|
|
||||||
if (myDurationMillis <= 0) {
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate the number of frames to capture
|
|
||||||
int numFramesToGet = numFrames;
|
|
||||||
long frameInterval = myDurationMillis / numFrames;
|
|
||||||
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
|
|
||||||
numFramesToGet = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each timeStamp, grap a frame
|
|
||||||
for (int i = 0; i < numFramesToGet; ++i) {
|
|
||||||
long timeStamp = i * frameInterval;
|
|
||||||
|
|
||||||
ret = playbin.pause();
|
|
||||||
if (ret == StateChangeReturn.FAILURE) {
|
|
||||||
// add this file to the set of known bad ones
|
|
||||||
badVideoFiles.add(file.getName());
|
|
||||||
throw new Exception("Problem with video file; problem when attempting to pause while capturing a frame.");
|
|
||||||
}
|
|
||||||
playbin.getState();
|
|
||||||
|
|
||||||
//System.out.println("Seeking to " + timeStamp + "milliseconds.");
|
|
||||||
if (!playbin.seek(timeStamp, unit)) {
|
|
||||||
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = playbin.play();
|
|
||||||
if (ret == StateChangeReturn.FAILURE) {
|
|
||||||
// add this file to the set of known bad ones
|
|
||||||
badVideoFiles.add(file.getName());
|
|
||||||
throw new Exception("Problem with video file; problem when attempting to play while capturing a frame.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for FrameCaptureRGBListener to finish
|
|
||||||
synchronized(lock) {
|
|
||||||
try {
|
|
||||||
lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Image image = rgbListener.getImage();
|
|
||||||
|
|
||||||
ret = playbin.stop();
|
|
||||||
if (ret == StateChangeReturn.FAILURE) {
|
|
||||||
// add this file to the set of known bad ones
|
|
||||||
badVideoFiles.add(file.getName());
|
|
||||||
throw new Exception("Problem with video file; problem when attempting to stop while capturing a frame.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image == null) {
|
|
||||||
logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName());
|
|
||||||
badVideoFiles.add(file.getName());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
frames.add(new VideoFrame(image, timeStamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FrameCaptureRGBListener implements RGBDataSink.Listener {
|
|
||||||
|
|
||||||
public FrameCaptureRGBListener(Object waiter) {
|
|
||||||
this.waiter = waiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BufferedImage bi;
|
|
||||||
private final Object waiter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
|
|
||||||
synchronized (waiter) {
|
|
||||||
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
|
|
||||||
waiter.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Image getImage() {
|
|
||||||
synchronized (waiter) {
|
|
||||||
Image image = bi;
|
|
||||||
bi = null;
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* regenerated by the Form Editor.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
|
||||||
private void initComponents() {
|
|
||||||
|
|
||||||
videoPanel = new javax.swing.JPanel();
|
|
||||||
controlPanel = new javax.swing.JPanel();
|
|
||||||
pauseButton = new javax.swing.JButton();
|
|
||||||
progressSlider = new javax.swing.JSlider();
|
|
||||||
progressLabel = new javax.swing.JLabel();
|
|
||||||
infoLabel = new javax.swing.JLabel();
|
|
||||||
|
|
||||||
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.setVerticalGroup(
|
|
||||||
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
||||||
.addGap(0, 188, Short.MAX_VALUE)
|
|
||||||
);
|
|
||||||
|
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
|
|
||||||
pauseButton.addActionListener(new java.awt.event.ActionListener() {
|
|
||||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
|
||||||
pauseButtonActionPerformed(evt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
|
|
||||||
|
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N
|
|
||||||
|
|
||||||
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
|
|
||||||
controlPanel.setLayout(controlPanelLayout);
|
|
||||||
controlPanelLayout.setHorizontalGroup(
|
|
||||||
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
||||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
|
||||||
.addComponent(pauseButton)
|
|
||||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
|
||||||
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE)
|
|
||||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
|
||||||
.addComponent(progressLabel)
|
|
||||||
.addContainerGap())
|
|
||||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
|
||||||
.addComponent(infoLabel)
|
|
||||||
.addGap(0, 0, Short.MAX_VALUE))
|
|
||||||
);
|
|
||||||
controlPanelLayout.setVerticalGroup(
|
|
||||||
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
||||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
|
||||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
||||||
.addComponent(pauseButton)
|
|
||||||
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
|
||||||
.addComponent(progressLabel, javax.swing.GroupLayout.Alignment.TRAILING))
|
|
||||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
|
||||||
.addComponent(infoLabel))
|
|
||||||
);
|
|
||||||
|
|
||||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
|
||||||
this.setLayout(layout);
|
|
||||||
layout.setHorizontalGroup(
|
|
||||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
||||||
.addComponent(videoPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
|
||||||
.addComponent(controlPanel, 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)
|
|
||||||
.addContainerGap())
|
|
||||||
);
|
|
||||||
}// </editor-fold>//GEN-END:initComponents
|
|
||||||
|
|
||||||
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
State state = gstPlaybin2.getState();
|
|
||||||
if (state.equals(State.PLAYING)) {
|
|
||||||
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pauseButton.setText("►");
|
|
||||||
// Is this call necessary considering we just called gstPlaybin2.pause()?
|
|
||||||
if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (state.equals(State.PAUSED)) {
|
|
||||||
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pauseButton.setText("||");
|
|
||||||
// Is this call necessary considering we just called gstPlaybin2.play()?
|
|
||||||
if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (state.equals(State.READY)) {
|
|
||||||
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
|
|
||||||
em.execute();
|
|
||||||
em.getExtractedBytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}//GEN-LAST:event_pauseButtonActionPerformed
|
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
|
||||||
private javax.swing.JPanel controlPanel;
|
|
||||||
private javax.swing.JLabel infoLabel;
|
|
||||||
private javax.swing.JButton pauseButton;
|
|
||||||
private javax.swing.JLabel progressLabel;
|
|
||||||
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 ";
|
|
||||||
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) {
|
|
||||||
return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetVideo() throws Exception {
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
if (gstPlaybin2 != null) {
|
|
||||||
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
}
|
|
||||||
// ready to be played again
|
|
||||||
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
}
|
|
||||||
gstPlaybin2.getState(); //NEW
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pauseButton.setText("►");
|
|
||||||
progressSlider.setValue(0);
|
|
||||||
|
|
||||||
String durationStr = String.format(durationFormat, 0, 0, 0,
|
|
||||||
totalHours, totalMinutes, totalSeconds);
|
|
||||||
progressLabel.setText(durationStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
private boolean hasNotEnded() {
|
|
||||||
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object doInBackground() throws Exception {
|
|
||||||
|
|
||||||
// enable the slider
|
|
||||||
progressSlider.setEnabled(true);
|
|
||||||
|
|
||||||
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
|
|
||||||
ClockTime pos = null;
|
|
||||||
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
|
|
||||||
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
pos = gstPlaybin2.queryPosition();
|
|
||||||
}
|
|
||||||
millisElapsed = pos.toMillis();
|
|
||||||
|
|
||||||
// pick out the elapsed hours, minutes, seconds
|
|
||||||
long secondsElapsed = millisElapsed / 1000;
|
|
||||||
elapsedHours = (int) secondsElapsed / 3600;
|
|
||||||
secondsElapsed -= elapsedHours * 3600;
|
|
||||||
elapsedMinutes = (int) secondsElapsed / 60;
|
|
||||||
secondsElapsed -= elapsedMinutes * 60;
|
|
||||||
elapsedSeconds = (int) secondsElapsed;
|
|
||||||
|
|
||||||
String durationStr = String.format(durationFormat,
|
|
||||||
elapsedHours, elapsedMinutes, elapsedSeconds,
|
|
||||||
totalHours, totalMinutes, totalSeconds);
|
|
||||||
|
|
||||||
progressLabel.setText(durationStr);
|
|
||||||
autoTracking = true;
|
|
||||||
progressSlider.setValue((int) millisElapsed);
|
|
||||||
autoTracking = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(INTER_FRAME_PERIOD_MS);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable the slider
|
|
||||||
progressSlider.setEnabled(false);
|
|
||||||
|
|
||||||
resetVideo();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} //end class progress worker
|
|
||||||
|
|
||||||
/* Thread that extracts and plays a file */
|
|
||||||
private class ExtractMedia extends SwingWorker<Object, Void> {
|
|
||||||
|
|
||||||
private ProgressHandle progress;
|
|
||||||
boolean success = false;
|
|
||||||
private AbstractFile sFile;
|
|
||||||
private java.io.File jFile;
|
|
||||||
private String duration;
|
|
||||||
private String position;
|
|
||||||
private long extractedBytes;
|
|
||||||
|
|
||||||
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
|
|
||||||
this.sFile = sFile;
|
|
||||||
this.jFile = jFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtractedBytes() {
|
|
||||||
return extractedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object doInBackground() throws Exception {
|
|
||||||
success = false;
|
|
||||||
progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() {
|
|
||||||
@Override
|
|
||||||
public boolean cancel() {
|
|
||||||
return ExtractMedia.this.cancel(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
progressLabel.setText("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);
|
|
||||||
}
|
|
||||||
success = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clean up or start the worker threads */
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
try {
|
|
||||||
super.get(); //block and get all exceptions thrown while doInBackground()
|
|
||||||
} catch (CancellationException ex) {
|
|
||||||
logger.log(Level.INFO, "Media buffering was canceled.");
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
logger.log(Level.INFO, "Media buffering was interrupted.");
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex);
|
|
||||||
} finally {
|
|
||||||
progress.finish();
|
|
||||||
if (!this.isCancelled()) {
|
|
||||||
playMedia();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void playMedia() {
|
|
||||||
if (jFile == null || !jFile.exists()) {
|
|
||||||
progressLabel.setText("Error buffering file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ClockTime dur = null;
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
// must play, then pause and get state to get duration.
|
|
||||||
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gstPlaybin2.getState();
|
|
||||||
dur = gstPlaybin2.queryDuration();
|
|
||||||
}
|
|
||||||
duration = dur.toString();
|
|
||||||
durationMillis = dur.toMillis();
|
|
||||||
|
|
||||||
// pick out the total hours, minutes, seconds
|
|
||||||
long durationSeconds = (int) durationMillis / 1000;
|
|
||||||
totalHours = (int) durationSeconds / 3600;
|
|
||||||
durationSeconds -= totalHours * 3600;
|
|
||||||
totalMinutes = (int) durationSeconds / 60;
|
|
||||||
durationSeconds -= totalMinutes * 60;
|
|
||||||
totalSeconds = (int) durationSeconds;
|
|
||||||
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
progressSlider.setMaximum((int) durationMillis);
|
|
||||||
progressSlider.setMinimum(0);
|
|
||||||
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pauseButton.setText("||");
|
|
||||||
videoProgressWorker = new VideoProgressWorker();
|
|
||||||
videoProgressWorker.execute();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -83,13 +83,22 @@ abstract class AbstractKeywordSearchPerformer extends javax.swing.JPanel impleme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void search() {
|
public void search() {
|
||||||
|
boolean isRunning = IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault());
|
||||||
|
|
||||||
if (filesIndexed == 0) {
|
if (filesIndexed == 0) {
|
||||||
KeywordSearchUtil.displayDialog("Keyword Search Error", "No files are indexed, please index an image before searching", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
|
if (isRunning) {
|
||||||
|
KeywordSearchUtil.displayDialog("Keyword Search Error", "<html>No files are in index yet. <br />"
|
||||||
|
+ "Try again later. Index is updated every " + KeywordSearchSettings.getUpdateFrequency().getTime() + " minutes.</html>", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
KeywordSearchUtil.displayDialog("Keyword Search Error", "<html>No files were indexed.<br />"
|
||||||
|
+ "Re-ingest the image with the Keyword Search Module enabled. </html>", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//check if keyword search module ingest is running (indexing, etc)
|
//check if keyword search module ingest is running (indexing, etc)
|
||||||
if (IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault())) {
|
if (isRunning) {
|
||||||
if (KeywordSearchUtil.displayConfirmDialog("Keyword Search Ingest in Progress",
|
if (KeywordSearchUtil.displayConfirmDialog("Keyword Search Ingest in Progress",
|
||||||
"<html>Keyword Search Ingest is currently running.<br />"
|
"<html>Keyword Search Ingest is currently running.<br />"
|
||||||
+ "Not all files have been indexed and this search might yield incomplete results.<br />"
|
+ "Not all files have been indexed and this search might yield incomplete results.<br />"
|
||||||
|
@ -43,7 +43,7 @@ import org.apache.solr.client.solrj.SolrServerException;
|
|||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keyword search toolbar which allows to search for single terms or phrases
|
* Keyword search toolbar (in upper right, by default) which allows to search for single terms or phrases
|
||||||
*
|
*
|
||||||
* The toolbar uses a different font from the rest of the application, Monospaced 14,
|
* The toolbar uses a different font from the rest of the application, Monospaced 14,
|
||||||
* due to the necessity to find a font that displays both Arabic and Asian fonts at an acceptable size.
|
* due to the necessity to find a font that displays both Arabic and Asian fonts at an acceptable size.
|
||||||
|
@ -3,72 +3,51 @@
|
|||||||
<!-- Need a way to specify TSK Debug versus Release -->
|
<!-- Need a way to specify TSK Debug versus Release -->
|
||||||
<property name="TSK_BUILD_TYPE">Release</property>
|
<property name="TSK_BUILD_TYPE">Release</property>
|
||||||
|
|
||||||
<target name="copyTSKLibsToRelease">
|
|
||||||
<property environment="env"/>
|
<target name="build-installer-windows" depends="init-advanced-installer"
|
||||||
<condition property="ewfFound">
|
description="Makes an installer from the opened ZIP file">
|
||||||
<isset property="env.LIBEWF_HOME"/>
|
<antcall target="run-advanced-installer" />
|
||||||
</condition>
|
<!--<delete dir="${nbdist.dir}/${app.name}-installer"/>-->
|
||||||
<fail unless="ewfFound" message="LIBEWF_HOME must be set as an environment variable."/>
|
<delete dir="${nbdist.dir}/installer_${app.name}-cache"/>
|
||||||
|
<move file="${nbdist.dir}/installer_${app.name}-SetupFiles/installer_${app.name}.msi" tofile="${nbdist.dir}/installer_${app.name}-${app.version}.msi" />
|
||||||
|
</target>
|
||||||
|
|
||||||
<copy file="${env.TSK_HOME}/win32/${TSK_BUILD_TYPE}/libtsk_jni.dll" tofile="${basedir}/Core/release/modules/lib/libtsk_jni.dll"/>
|
<target name="init-advanced-installer" depends="autoAIPath,inputAIPath"
|
||||||
<copy file="${env.LIBEWF_HOME}/msvscpp/Release/libewf.dll" tofile="${basedir}/Core/release/modules/lib/libewf.dll"/>
|
description="Find AdvancedInstaller executable.">
|
||||||
<copy file="${env.LIBEWF_HOME}/msvscpp/Release/zlib.dll" tofile="${basedir}/Core/release/modules/lib/zlib.dll"/>
|
<fail unless="ai-exe-path" message="Could not locate Advanced Installer."/>
|
||||||
|
<!-- Copy the template file to add details to -->
|
||||||
|
<copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${nbdist.dir}/installer_${app.name}.aip" overwrite="true"/>
|
||||||
|
<property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/>
|
||||||
|
<property name="aip-path" value="${nbdist.dir}\installer_${app.name}.aip"/>
|
||||||
|
<echo message="${ai-exe-path}" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="copyExternalLibsToZip">
|
<target name="autoAIPath" description="Attempt to find the AI path based on standard installation location">
|
||||||
|
<property name="AI.path" value="C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com" />
|
||||||
<!-- Find CRT version we linked against from libtsk_jni manifest -->
|
|
||||||
<!-- disable auto-detection for CRT100
|
|
||||||
<property name="libtsk.manifest.path">${env.TSK_HOME}/win32/tsk_jni/${TSK_BUILD_TYPE}/libtsk_jni.dll.intermediate.manifest</property>
|
|
||||||
<loadfile property="libtsk.manifest" srcFile="${libtsk.manifest.path}" />
|
|
||||||
<propertyregex property="CRT.version"
|
|
||||||
input="${libtsk.manifest}"
|
|
||||||
regexp=".*Microsoft\.VC90.*?version\s*?=\s*?'(.*?)'"
|
|
||||||
select="\1"
|
|
||||||
casesensitive="false" />
|
|
||||||
<echo>Found CRT.version linked against: ${CRT.version}</echo>
|
|
||||||
-->
|
|
||||||
<!-- <property name="CRT.version">10.0.40219.1</property> -->
|
|
||||||
<!-- <property name="CRT.version">10.0.40219.325</property> -->
|
|
||||||
<property name="CRT.version">10.0.30319.1</property>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Get C++ Runtime dlls -->
|
|
||||||
<property environment="env"/>
|
|
||||||
<condition property="crtDetected">
|
|
||||||
<isset property="CRT.version"/>
|
|
||||||
</condition>
|
|
||||||
<fail unless="crtDetected" message="CRT version not detected, check libtsk_jni manifest."/>
|
|
||||||
|
|
||||||
<property name="CRT.path">${thirdparty.dir}/crt/x86-32/${CRT.version}/crt.zip</property>
|
|
||||||
<available file="${CRT.path}" property="crtFound"/>
|
|
||||||
|
|
||||||
<fail unless="crtFound" message="Detected CRT version ${CRT.version} not found in the thirdparty repo in path: ${CRT.path}"/>
|
|
||||||
|
|
||||||
<!-- unzip from thirdparty repo to modules/lib in staged dir -->
|
|
||||||
<!-- <unzip src="${CRT.path}" dest="${zip-tmp}/${app.name}/bin"/> -->
|
|
||||||
<unzip src="${CRT.path}" dest="${zip-tmp}/${app.name}/${app.name}/modules/lib"/>
|
|
||||||
|
|
||||||
<!-- delete 64 bit exe for now -->
|
|
||||||
<delete file="${zip-tmp}/${app.name}/bin/${app.name}64.exe" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="autoAIPath" >
|
|
||||||
<property name="AI.path">C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com</property>
|
|
||||||
<available file="${AI.path}"
|
<available file="${AI.path}"
|
||||||
property="ai-exe-path"
|
property="ai-exe-path"
|
||||||
value="${AI.path}"/>
|
value="${AI.path}"/>
|
||||||
|
<echo message="${ai-exe-path}" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="inputAIPath" unless="ai-exe-path">
|
<target name="inputAIPath" unless="ai-exe-path" description="Have the user input the path to the AI executable">
|
||||||
<input addProperty="ai-exe-path"
|
<input addProperty="ai-exe-path"
|
||||||
message="Enter the location of AdvancedInstaller.com"/>
|
message="Enter the location of AdvancedInstaller.com"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="run-advanced-installer" depends="autoAIPath,inputAIPath">
|
<target name="run-advanced-installer" depends="add-ai-productinfo,add-ai-files,add-ai-shortcuts,add-ai-env">
|
||||||
<fail unless="ai-exe-path" message="Could not locate Advanced Installer."/>
|
<!-- Leaving this commented out bit for documentation purposes. Not sure what its supposed to do. -->
|
||||||
<!-- Copy the template file to add details to -->
|
<!-- Need to find a way to deal with beta version -->
|
||||||
<copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${nbdist.dir}/installer_${app.name}.aip" overwrite="true"/>
|
<!--<echo message="Setting ${app.name} version to ${app.version}..."/>
|
||||||
|
<exec executable="${ai-exe-path}">
|
||||||
|
<arg line="/edit ${aip-path} /SetVersion ${app.version}"/>
|
||||||
|
</exec>-->
|
||||||
|
|
||||||
|
|
||||||
|
<!--<delete file="${aip-path}"/>-->
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="add-ai-productinfo" description="Add product information to the aip file">
|
||||||
<scriptdef name="generateguid" language="javascript">
|
<scriptdef name="generateguid" language="javascript">
|
||||||
<attribute name="property" />
|
<attribute name="property" />
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
@ -78,8 +57,6 @@
|
|||||||
]]>
|
]]>
|
||||||
</scriptdef>
|
</scriptdef>
|
||||||
<generateguid property="guid1" />
|
<generateguid property="guid1" />
|
||||||
<property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/>
|
|
||||||
<property name="aip-path" value="${nbdist.dir}\installer_${app.name}.aip"/>
|
|
||||||
<!-- automatically replace version name and productcode in the .aip file -->
|
<!-- automatically replace version name and productcode in the .aip file -->
|
||||||
<echo>Product Code: ${guid1}</echo>
|
<echo>Product Code: ${guid1}</echo>
|
||||||
<echo>Product Version: ${app.version}</echo>
|
<echo>Product Version: ${app.version}</echo>
|
||||||
@ -92,55 +69,36 @@
|
|||||||
<replaceregexp file="${aip-path}"
|
<replaceregexp file="${aip-path}"
|
||||||
match="ProductVersion" Value="\d+\.{1}\d+\.{1}\d+"
|
match="ProductVersion" Value="\d+\.{1}\d+\.{1}\d+"
|
||||||
replace="ProductVersion" Value="${app.version}" />
|
replace="ProductVersion" Value="${app.version}" />
|
||||||
|
</target>
|
||||||
<!-- Use Advanced Installer to configure files to add -->
|
|
||||||
<echo message="Adding files to installer..."/>
|
<target name="add-ai-files" description="Add the files in the installation path to the installer file">
|
||||||
|
<foreach target="add-file-or-dir" param="theFile" inheritall="true" inheritrefs="true">
|
||||||
|
<path>
|
||||||
|
<fileset dir="${inst-path}">
|
||||||
|
<include name="*" />
|
||||||
|
</fileset>
|
||||||
|
<dirset dir="${inst-path}">
|
||||||
|
<include name="*"/>
|
||||||
|
</dirset>
|
||||||
|
</path>
|
||||||
|
</foreach>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="add-file-or-dir" depends="is-file-or-folder">
|
||||||
|
<echo message="${ai-exe-path}" />
|
||||||
|
<echo message="Adding ${file-or-folder} to installer: ${theFile}"/>
|
||||||
<exec executable="${ai-exe-path}">
|
<exec executable="${ai-exe-path}">
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\bin"/>
|
<arg line="/edit ${aip-path} /Add${file-or-folder} APPDIR ${theFile}" />
|
||||||
</exec>
|
</exec>
|
||||||
<exec executable="${ai-exe-path}">
|
</target>
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\etc"/>
|
|
||||||
</exec>
|
<target name="is-file-or-folder">
|
||||||
<exec executable="${ai-exe-path}">
|
<condition property="file-or-folder" value="File" else="Folder">
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\gstreamer"/>
|
<available file="${theFile}" type="file" />
|
||||||
</exec>
|
</condition>
|
||||||
<exec executable="${ai-exe-path}">
|
</target>
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\harness"/>
|
|
||||||
</exec>
|
<target name="add-ai-shortcuts" description="Add shortcuts to the aip file">
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\java"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\jre"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\platform"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\${app.name}"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\icon.ico"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\KNOWN_ISSUES.txt"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\LICENSE-2.0.txt"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\NEWS.txt"/>
|
|
||||||
</exec>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\README.txt"/>
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Need to find a way to deal with beta version -->
|
|
||||||
<!--<echo message="Setting ${app.name} version to ${app.version}..."/>
|
|
||||||
<exec executable="${ai-exe-path}">
|
|
||||||
<arg line="/edit ${aip-path} /SetVersion ${app.version}"/>
|
|
||||||
</exec>-->
|
|
||||||
|
|
||||||
<echo message="Adding desktop/menu shortcuts..."/>
|
<echo message="Adding desktop/menu shortcuts..."/>
|
||||||
<exec executable="${ai-exe-path}">
|
<exec executable="${ai-exe-path}">
|
||||||
<arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir DesktopFolder -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/>
|
<arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir DesktopFolder -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/>
|
||||||
@ -148,7 +106,10 @@
|
|||||||
<exec executable="${ai-exe-path}">
|
<exec executable="${ai-exe-path}">
|
||||||
<arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir SHORTCUTDIR -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/>
|
<arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir SHORTCUTDIR -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/>
|
||||||
</exec>
|
</exec>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<!-- TODO: does this always need to be done, or just for 64 bit files? -->
|
||||||
|
<target name="add-ai-env" description="Add the enviornment variables to the aip file">
|
||||||
<echo message="Setting environment variables..."/>
|
<echo message="Setting environment variables..."/>
|
||||||
<exec executable="${ai-exe-path}">
|
<exec executable="${ai-exe-path}">
|
||||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/>
|
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||||
@ -162,14 +123,6 @@
|
|||||||
<exec executable="${ai-exe-path}">
|
<exec executable="${ai-exe-path}">
|
||||||
<arg line="/build ${aip-path}"/>
|
<arg line="/build ${aip-path}"/>
|
||||||
</exec>
|
</exec>
|
||||||
<!--<delete file="${aip-path}"/>-->
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- Makes an installer from the opened ZIP file -->
|
|
||||||
<target name="build-installer-windows">
|
|
||||||
<antcall target="run-advanced-installer" />
|
|
||||||
<!--<delete dir="${nbdist.dir}/${app.name}-installer"/>-->
|
|
||||||
<delete dir="${nbdist.dir}/installer_${app.name}-cache"/>
|
|
||||||
<move file="${nbdist.dir}/installer_${app.name}-SetupFiles/installer_${app.name}.msi" tofile="${nbdist.dir}/installer_${app.name}-${app.version}.msi" />
|
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
24
build.xml
24
build.xml
@ -54,10 +54,6 @@
|
|||||||
</condition>
|
</condition>
|
||||||
<fail unless="jreFound" message="JRE_HOME must be set as an environment variable."/>
|
<fail unless="jreFound" message="JRE_HOME must be set as an environment variable."/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="getExternals" depends="findTSK">
|
|
||||||
<antcall target="copyTSKLibsToRelease" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="getJunit">
|
<target name="getJunit">
|
||||||
<property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" />
|
<property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" />
|
||||||
@ -87,9 +83,7 @@
|
|||||||
<copy todir="${zip-tmp}/${app.name}/jre">
|
<copy todir="${zip-tmp}/${app.name}/jre">
|
||||||
<fileset dir="${env.JRE_HOME}"/>
|
<fileset dir="${env.JRE_HOME}"/>
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy file="${basedir}/branding_${app.name}/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/>
|
<copy file="${basedir}/branding_${app.name}/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/>
|
||||||
<antcall target="copyExternalLibsToZip"></antcall>
|
|
||||||
|
|
||||||
<property name="app.property.file" value="${zip-tmp}/${app.name}/etc/${app.name}.conf" />
|
<property name="app.property.file" value="${zip-tmp}/${app.name}/etc/${app.name}.conf" />
|
||||||
<property name="jvm.options" value=""--branding ${app.name} -J-Xms24m -J-Xmx512m -J-XX:MaxPermSize=128M -J-Xverify:none"" />
|
<property name="jvm.options" value=""--branding ${app.name} -J-Xms24m -J-Xmx512m -J-XX:MaxPermSize=128M -J-Xverify:none"" />
|
||||||
@ -101,6 +95,18 @@
|
|||||||
<!-- workaround for ant escaping : and = when setting properties -->
|
<!-- workaround for ant escaping : and = when setting properties -->
|
||||||
<replace file="${app.property.file}" token="@JVM_OPTIONS" value="${jvm.options}" />
|
<replace file="${app.property.file}" token="@JVM_OPTIONS" value="${jvm.options}" />
|
||||||
|
|
||||||
|
<!-- We want to remove the dlls in autopsy/modules/lib because they will
|
||||||
|
shadow the files in the autopsy/modules/lib/ARCHITECTURE folder in the JAR.
|
||||||
|
These files are legacy from when we used to copy the dlls to this location.
|
||||||
|
This check should do away in the future. Added Sept '13-->
|
||||||
|
<delete failonerror="false">
|
||||||
|
<fileset dir="${zip-tmp}/${app.name}/autopsy/modules/lib">
|
||||||
|
<include name="libtsk_jni.dll" />
|
||||||
|
<include name="libewf.dll" />
|
||||||
|
<include name="zlib.dll" />
|
||||||
|
</fileset>
|
||||||
|
</delete>
|
||||||
|
|
||||||
<!-- step (4) zip again, but with the version numbers in the dir -->
|
<!-- step (4) zip again, but with the version numbers in the dir -->
|
||||||
<zip destfile="${nbdist.dir}/${app.name}-${app.version}.zip">
|
<zip destfile="${nbdist.dir}/${app.name}-${app.version}.zip">
|
||||||
<zipfileset dir="${zip-tmp}/${app.name}"/>
|
<zipfileset dir="${zip-tmp}/${app.name}"/>
|
||||||
@ -133,7 +139,7 @@
|
|||||||
<property name="app.version" value="${DSTAMP}"/>
|
<property name="app.version" value="${DSTAMP}"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="-init" depends="-taskdefs,-convert-old-project,getExternals,getProps,getJunit">
|
<target name="-init" depends="-taskdefs,-convert-old-project,getProps,getJunit">
|
||||||
<convertclusterpath from="${cluster.path.evaluated}" to="cluster.path.final" id="cluster.path.id"/>
|
<convertclusterpath from="${cluster.path.evaluated}" to="cluster.path.final" id="cluster.path.id"/>
|
||||||
<sortsuitemodules unsortedmodules="${modules}" sortedmodulesproperty="modules.sorted"/>
|
<sortsuitemodules unsortedmodules="${modules}" sortedmodulesproperty="modules.sorted"/>
|
||||||
<property name="cluster" location="build/cluster"/>
|
<property name="cluster" location="build/cluster"/>
|
||||||
@ -231,9 +237,8 @@
|
|||||||
</exec>
|
</exec>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="versioning-script" depends="check-release"/> <!--, versioning-script-if-release, versioning-script-if-not-release"/>-->
|
<target name="versioning-script" depends="check-release, versioning-script-if-release, versioning-script-if-not-release"/>
|
||||||
|
|
||||||
<!-- opens the zip file into a folder in dist -->
|
|
||||||
<target name="build-installer-dir" depends="getProps, versioning-script, build-zip" description="Builds Autopsy with branding and all dependencies" >
|
<target name="build-installer-dir" depends="getProps, versioning-script, build-zip" description="Builds Autopsy with branding and all dependencies" >
|
||||||
<unzip src="${nbdist.dir}/${app.name}-${app.version}.zip" dest="${nbdist.dir}/${app.name}-installer"/>
|
<unzip src="${nbdist.dir}/${app.name}-${app.version}.zip" dest="${nbdist.dir}/${app.name}-installer"/>
|
||||||
</target>
|
</target>
|
||||||
@ -241,7 +246,6 @@
|
|||||||
<target name="build-installer" depends="build-installer-dir" description="Builds Autopsy installer.">
|
<target name="build-installer" depends="build-installer-dir" description="Builds Autopsy installer.">
|
||||||
<antcall target="build-installer-${os.family}" />
|
<antcall target="build-installer-${os.family}" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="test-download-imgs" description="Get test images and store them in the path represented by the test-input variable.">
|
<target name="test-download-imgs" description="Get test images and store them in the path represented by the test-input variable.">
|
||||||
<available file="${test-input}/nps-2008-jean.E01" property="img-present-1"/>
|
<available file="${test-input}/nps-2008-jean.E01" property="img-present-1"/>
|
||||||
<available file="${test-input}/nps-2008-jean.E02" property="img-present-2"/>
|
<available file="${test-input}/nps-2008-jean.E02" property="img-present-2"/>
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
#
|
#
|
||||||
# http://wiki.sleuthkit.org/index.php?title=Autopsy_3_Module_Versions
|
# http://wiki.sleuthkit.org/index.php?title=Autopsy_3_Module_Versions
|
||||||
#
|
#
|
||||||
# The basic idea is that this script uses javadoc/jdiff to
|
# The basic idea is that this script uses javadoc/jdiff to
|
||||||
# compare the current state of the source code to the last
|
# compare the current state of the source code to the last
|
||||||
# tag and identifies if APIs were removed, added, etc.
|
# tag and identifies if APIs were removed, added, etc.
|
||||||
#
|
#
|
||||||
# When run from the Autopsy build script, this script will:
|
# When run from the Autopsy build script, this script will:
|
||||||
# - Clone Autopsy and checkout to the previous release tag
|
# - Clone Autopsy and checkout to the previous release tag
|
||||||
@ -61,6 +61,12 @@ from shutil import move
|
|||||||
from tempfile import mkstemp
|
from tempfile import mkstemp
|
||||||
from xml.dom.minidom import parse, parseString
|
from xml.dom.minidom import parse, parseString
|
||||||
|
|
||||||
|
# Jdiff return codes. Described in more detail further on
|
||||||
|
NO_CHANGES = 100
|
||||||
|
COMPATIBLE = 101
|
||||||
|
NON_COMPATIBLE = 102
|
||||||
|
ERROR = 1
|
||||||
|
|
||||||
# An Autopsy module object
|
# An Autopsy module object
|
||||||
class Module:
|
class Module:
|
||||||
# Initialize it with a name, return code, and version numbers
|
# Initialize it with a name, return code, and version numbers
|
||||||
@ -218,11 +224,11 @@ def compare_xml(module, apiname_tag, apiname_cur):
|
|||||||
log.close()
|
log.close()
|
||||||
code = jdiff.returncode
|
code = jdiff.returncode
|
||||||
print("Compared XML for " + module.name)
|
print("Compared XML for " + module.name)
|
||||||
if code == 100:
|
if code == NO_CHANGES:
|
||||||
print(" No API changes")
|
print(" No API changes")
|
||||||
elif code == 101:
|
elif code == COMPATIBLE:
|
||||||
print(" API Changes are backwards compatible")
|
print(" API Changes are backwards compatible")
|
||||||
elif code == 102:
|
elif code == NON_COMPATIBLE:
|
||||||
print(" API Changes are not backwards compatible")
|
print(" API Changes are not backwards compatible")
|
||||||
else:
|
else:
|
||||||
print(" *Error in XML, most likely an empty module")
|
print(" *Error in XML, most likely an empty module")
|
||||||
@ -564,18 +570,18 @@ def update_versions(modules, source):
|
|||||||
if manifest == None or project == None:
|
if manifest == None or project == None:
|
||||||
print(" Error finding manifeset and project properties files")
|
print(" Error finding manifeset and project properties files")
|
||||||
return
|
return
|
||||||
if module.ret == 101:
|
if module.ret == COMPATIBLE:
|
||||||
versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]]
|
versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]]
|
||||||
set_specification(project, manifest, versions[0])
|
set_specification(project, manifest, versions[0])
|
||||||
set_implementation(manifest, versions[1])
|
set_implementation(manifest, versions[1])
|
||||||
module.set_versions(versions)
|
module.set_versions(versions)
|
||||||
elif module.ret == 102:
|
elif module.ret == NON_COMPATIBLE:
|
||||||
versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1]
|
versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1]
|
||||||
set_specification(project, manifest, versions[0])
|
set_specification(project, manifest, versions[0])
|
||||||
set_implementation(manifest, versions[1])
|
set_implementation(manifest, versions[1])
|
||||||
set_release(manifest, versions[2])
|
set_release(manifest, versions[2])
|
||||||
module.set_versions(versions)
|
module.set_versions(versions)
|
||||||
elif module.ret == 100:
|
elif module.ret == NO_CHANGES:
|
||||||
versions = [versions[0], versions[1] + 1, versions[2]]
|
versions = [versions[0], versions[1] + 1, versions[2]]
|
||||||
set_implementation(manifest, versions[1])
|
set_implementation(manifest, versions[1])
|
||||||
module.set_versions(versions)
|
module.set_versions(versions)
|
||||||
@ -624,48 +630,40 @@ def print_version_updates(modules):
|
|||||||
f = open("gen_version.txt", "a")
|
f = open("gen_version.txt", "a")
|
||||||
for module in modules:
|
for module in modules:
|
||||||
versions = module.versions
|
versions = module.versions
|
||||||
if module.ret == 101:
|
if module.ret == COMPATIBLE:
|
||||||
output = (module.name + ":\n")
|
output = (module.name + ":\n")
|
||||||
output += (" Current Specification version:\t" + str(versions[0]) + "\n")
|
output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].increment()) + "\n")
|
||||||
output += (" Updated Specification version:\t" + str(versions[0].increment()) + "\n")
|
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
|
||||||
output += ("\n")
|
output += ("\tRelease:\tNo Change.\n")
|
||||||
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
|
|
||||||
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
|
|
||||||
output += ("\n")
|
output += ("\n")
|
||||||
print(output)
|
print(output)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
f.write(output)
|
f.write(output)
|
||||||
elif module.ret == 102:
|
elif module.ret == NON_COMPATIBLE:
|
||||||
output = (module.name + ":\n")
|
output = (module.name + ":\n")
|
||||||
output += (" Current Specification version:\t" + str(versions[0]) + "\n")
|
output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].overflow()) + "\n")
|
||||||
output += (" Updated Specification version:\t" + str(versions[0].overflow()) + "\n")
|
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
|
||||||
output += ("\n")
|
output += ("\tRelease:\t" + str(versions[2]) + "\t->\t" + str(versions[2] + 1) + "\n")
|
||||||
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
|
|
||||||
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
|
|
||||||
output += ("\n")
|
|
||||||
output += (" Current Release version:\t\t" + str(versions[2]) + "\n")
|
|
||||||
output += (" Updated Release version:\t\t" + str(versions[2] + 1) + "\n")
|
|
||||||
output += ("\n")
|
output += ("\n")
|
||||||
print(output)
|
print(output)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
f.write(output)
|
f.write(output)
|
||||||
elif module.ret == 1:
|
elif module.ret == ERROR:
|
||||||
output = (module.name + ":\n")
|
output = (module.name + ":\n")
|
||||||
output += (" *Unable to detect necessary changes\n")
|
output += ("\t*Unable to detect necessary changes\n")
|
||||||
output += (" Current Specification version:\t" + str(versions[0]) + "\n")
|
output += ("\tSpecification:\t" + str(versions[0]) + "\n")
|
||||||
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
|
output += ("\tImplementation:\t" + str(versions[1]) + "\n")
|
||||||
output += (" Current Release version:\t\t" + str(versions[2]) + "\n")
|
output += ("\tRelease:\t\t" + str(versions[2]) + "\n")
|
||||||
output += ("\n")
|
output += ("\n")
|
||||||
print(output)
|
print(output)
|
||||||
f.write(output)
|
f.write(output)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
elif module.ret == 100:
|
elif module.ret == NO_CHANGES:
|
||||||
output = (module.name + ":\n")
|
output = (module.name + ":\n")
|
||||||
if versions[1] is None:
|
if versions[1] is None:
|
||||||
output += (" No Implementation version.\n")
|
output += ("\tImplementation: None\n")
|
||||||
else:
|
else:
|
||||||
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
|
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
|
||||||
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
|
|
||||||
output += ("\n")
|
output += ("\n")
|
||||||
print(output)
|
print(output)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
@ -673,16 +671,13 @@ def print_version_updates(modules):
|
|||||||
elif module.ret is None:
|
elif module.ret is None:
|
||||||
output = ("Added " + module.name + ":\n")
|
output = ("Added " + module.name + ":\n")
|
||||||
if module.spec() != "1.0" and module.spec() != "0.0":
|
if module.spec() != "1.0" and module.spec() != "0.0":
|
||||||
output += (" Current Specification version:\t" + str(module.spec()) + "\n")
|
output += ("\tSpecification:\t" + str(module.spec()) + "\t->\t" + "1.0\n")
|
||||||
output += (" Updated Specification version:\t1.0\n")
|
|
||||||
output += ("\n")
|
output += ("\n")
|
||||||
if module.impl() != 1:
|
if module.impl() != 1:
|
||||||
output += (" Current Implementation version:\t" + str(module.impl()) + "\n")
|
output += ("\tImplementation:\t" + str(module.impl()) + "\t->\t" + "1\n")
|
||||||
output += (" Updated Implementation version:\t1\n")
|
|
||||||
output += ("\n")
|
output += ("\n")
|
||||||
if module.release() != 1 and module.release() != 0:
|
if module.release() != 1 and module.release() != 0:
|
||||||
output += (" Current Release version:\t\t" + str(module.release()) + "\n")
|
output += ("Release:\t\t" + str(module.release()) + "\t->\t" + "1\n")
|
||||||
output += (" Updated Release version:\t\t1\n")
|
|
||||||
output += ("\n")
|
output += ("\n")
|
||||||
print(output)
|
print(output)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user