mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 01:07: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.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
|
||||
/**
|
||||
* 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 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() {
|
||||
javaFxInit = true;
|
||||
packageInstallers = new ArrayList<ModuleInstall>();
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 Basis Technology Corp.
|
||||
* Copyright 2011-2013 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* 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 javax.swing.JMenuItem;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.text.html.HTMLEditorKit;
|
||||
import javax.swing.text.html.StyleSheet;
|
||||
import org.openide.nodes.Node;
|
||||
@ -56,7 +56,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
||||
private static int currentPage = 1;
|
||||
private List<BlackboardArtifact> artifacts;
|
||||
private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName());
|
||||
|
||||
private final static String WAIT_TEXT = "Preparing display...";
|
||||
|
||||
/** Creates new form DataContentViewerArtifact */
|
||||
public DataContentViewerArtifact() {
|
||||
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
|
||||
currentPage = currentPage+1;
|
||||
setDataView(artifacts, currentPage);
|
||||
new DisplayTask(currentPage).execute();
|
||||
}//GEN-LAST:event_nextPageButtonActionPerformed
|
||||
|
||||
private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
|
||||
currentPage = currentPage-1;
|
||||
setDataView(artifacts, currentPage);
|
||||
new DisplayTask(currentPage).execute();
|
||||
}//GEN-LAST:event_prevPageButtonActionPerformed
|
||||
|
||||
// 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);
|
||||
if (content == null) {
|
||||
resetComponent();
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
this.setDataView(content.getAllArtifacts(), 1);
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.WARNING, "Couldn't get artifacts: ", ex);
|
||||
artifacts = content.getAllArtifacts();
|
||||
}
|
||||
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
|
||||
int index = 0;
|
||||
BlackboardArtifact artifact = lookup.lookup(BlackboardArtifact.class);
|
||||
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
|
||||
@ -279,7 +290,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
||||
public void resetComponent() {
|
||||
// clear / reset the fields
|
||||
currentPage = 1;
|
||||
this.artifacts = new ArrayList<BlackboardArtifact>();
|
||||
this.artifacts = new ArrayList<>();
|
||||
currentPageLabel.setText("");
|
||||
totalPageLabel.setText("");
|
||||
outputViewPane.setText("");
|
||||
@ -311,12 +322,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
||||
return false;
|
||||
}
|
||||
|
||||
ArtifactStringContent artifact = node.getLookup().lookup(ArtifactStringContent.class);
|
||||
Content content = node.getLookup().lookup(Content.class);
|
||||
|
||||
if(artifact != null) {
|
||||
return true;
|
||||
}
|
||||
Content content = node.getLookup().lookup(Content.class);
|
||||
if(content != null) {
|
||||
try {
|
||||
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 offset Index into the list for the artifact to display
|
||||
*/
|
||||
private void setDataView(List<BlackboardArtifact> artifacts, int offset) {
|
||||
// change the cursor to "waiting cursor" for this operation
|
||||
private void setDataView(int offset) {
|
||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
|
||||
outputViewPane.setText(WAIT_TEXT);
|
||||
|
||||
if(artifacts.isEmpty()){
|
||||
resetComponent();
|
||||
return;
|
||||
}
|
||||
this.artifacts = artifacts;
|
||||
StringContent artifactString = new ArtifactStringContent(artifacts.get(offset-1));
|
||||
String text = artifactString.getString();
|
||||
|
||||
@ -406,16 +411,19 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
||||
setComponentsVisibility(true);
|
||||
outputViewPane.moveCaretPosition(0);
|
||||
this.setCursor(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the displayed artifact to the specified one.
|
||||
* @param artifact Artifact to display
|
||||
*/
|
||||
private void setSelectedArtifact(BlackboardArtifact artifact) {
|
||||
if(artifacts.contains(artifact)) {
|
||||
int index = artifacts.indexOf(artifact);
|
||||
setDataView(artifacts, index+1);
|
||||
private class DisplayTask extends SwingWorker<Integer, Void> {
|
||||
final int pageIndex;
|
||||
|
||||
DisplayTask(final int pageIndex) {
|
||||
this.pageIndex = pageIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@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_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
|
||||
|
@ -43,7 +43,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
||||
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
|
||||
|
||||
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 Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
|
||||
@ -65,7 +65,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
|
||||
initComponents();
|
||||
|
||||
videoPanel = new MediaViewVideoPanel();
|
||||
// get the right panel for our platform
|
||||
videoPanel = MediaViewVideoPanel.createVideoPanel();
|
||||
|
||||
imagePanel = new MediaViewImagePanel();
|
||||
videoPanelInited = videoPanel.isInited();
|
||||
imagePanelInited = imagePanel.isInited();
|
||||
@ -75,14 +77,14 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
|
||||
//initialize supported image types
|
||||
//TODO use mime-types instead once we have support
|
||||
String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes();
|
||||
IMAGES = new String[fxSupportedImagesSuffixes.length];
|
||||
//logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
|
||||
for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) {
|
||||
String suffix = fxSupportedImagesSuffixes[i];
|
||||
logger.log(Level.INFO, "suffix: " + suffix);
|
||||
//logger.log(Level.INFO, "suffix: " + suffix);
|
||||
IMAGES[i] = "." + suffix;
|
||||
}
|
||||
|
||||
@ -110,35 +112,38 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
|
||||
@Override
|
||||
public void setNode(Node selectedNode) {
|
||||
if (selectedNode == null) {
|
||||
resetComponent();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (selectedNode == null) {
|
||||
resetComponent();
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class);
|
||||
if (file == null) {
|
||||
resetComponent();
|
||||
return;
|
||||
}
|
||||
AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class);
|
||||
if (file == null) {
|
||||
resetComponent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.equals(lastFile)) {
|
||||
return; //prevent from loading twice if setNode() called mult. times
|
||||
} else {
|
||||
if (file.equals(lastFile)) {
|
||||
return; //prevent from loading twice if setNode() called mult. times
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception while setting node", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,12 +182,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
|
||||
@Override
|
||||
public void resetComponent() {
|
||||
lastFile = null;
|
||||
videoPanel.reset();
|
||||
// @@@ Seems like we should also reset the image viewer...
|
||||
lastFile = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Node node) {
|
||||
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;
|
||||
|
||||
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.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import java.util.logging.Logger;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
@ServiceProvider(service = FrameCapture.class)
|
||||
})
|
||||
public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture {
|
||||
|
||||
public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.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>());
|
||||
|
||||
// 64 bit architectures
|
||||
private static final String[] ARCH64 = new String[]{"amd64", "x86_64"};
|
||||
|
||||
// 32 bit architectures
|
||||
private static final String[] ARCH32 = new String[]{"x86"};
|
||||
|
||||
// A Gstreamer implementation of MediaViewVideoPanel
|
||||
private static GstVideoPanel gstVideoPanel = null;
|
||||
|
||||
// A JavaFX implmentation of MediaViewVideoPanel
|
||||
private static FXVideoPanel fxVideoPanel = null;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isInited() {
|
||||
return gstInited;
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
if (!initGst()) {
|
||||
return;
|
||||
private static MediaViewVideoPanel getGstImpl() {
|
||||
if (gstVideoPanel == null) {
|
||||
gstVideoPanel = new 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return gstVideoPanel;
|
||||
}
|
||||
|
||||
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;
|
||||
/**
|
||||
* Get a JavaFX video player implementation.
|
||||
*
|
||||
* @return a FXVideoPanel
|
||||
*/
|
||||
private static MediaViewVideoPanel getFXImpl() {
|
||||
if (fxVideoPanel == null) {
|
||||
fxVideoPanel = new FXVideoPanel();
|
||||
}
|
||||
|
||||
return true;
|
||||
return fxVideoPanel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param file video file to play
|
||||
* @param dims dimension of the parent window
|
||||
*/
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
abstract void setupVideo(final AbstractFile file, final Dimension dims);
|
||||
}
|
||||
|
@ -83,13 +83,22 @@ abstract class AbstractKeywordSearchPerformer extends javax.swing.JPanel impleme
|
||||
|
||||
@Override
|
||||
public void search() {
|
||||
boolean isRunning = IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//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",
|
||||
"<html>Keyword Search Ingest is currently running.<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;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* 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 -->
|
||||
<property name="TSK_BUILD_TYPE">Release</property>
|
||||
|
||||
<target name="copyTSKLibsToRelease">
|
||||
<property environment="env"/>
|
||||
<condition property="ewfFound">
|
||||
<isset property="env.LIBEWF_HOME"/>
|
||||
</condition>
|
||||
<fail unless="ewfFound" message="LIBEWF_HOME must be set as an environment variable."/>
|
||||
|
||||
<target name="build-installer-windows" depends="init-advanced-installer"
|
||||
description="Makes an installer from the opened ZIP file">
|
||||
<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>
|
||||
|
||||
<copy file="${env.TSK_HOME}/win32/${TSK_BUILD_TYPE}/libtsk_jni.dll" tofile="${basedir}/Core/release/modules/lib/libtsk_jni.dll"/>
|
||||
<copy file="${env.LIBEWF_HOME}/msvscpp/Release/libewf.dll" tofile="${basedir}/Core/release/modules/lib/libewf.dll"/>
|
||||
<copy file="${env.LIBEWF_HOME}/msvscpp/Release/zlib.dll" tofile="${basedir}/Core/release/modules/lib/zlib.dll"/>
|
||||
<target name="init-advanced-installer" depends="autoAIPath,inputAIPath"
|
||||
description="Find AdvancedInstaller executable.">
|
||||
<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 name="copyExternalLibsToZip">
|
||||
|
||||
<!-- 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>
|
||||
<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" />
|
||||
<available file="${AI.path}"
|
||||
property="ai-exe-path"
|
||||
value="${AI.path}"/>
|
||||
<echo message="${ai-exe-path}" />
|
||||
</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"
|
||||
message="Enter the location of AdvancedInstaller.com"/>
|
||||
</target>
|
||||
|
||||
<target name="run-advanced-installer" depends="autoAIPath,inputAIPath">
|
||||
<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"/>
|
||||
<target name="run-advanced-installer" depends="add-ai-productinfo,add-ai-files,add-ai-shortcuts,add-ai-env">
|
||||
<!-- Leaving this commented out bit for documentation purposes. Not sure what its supposed to do. -->
|
||||
<!-- 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>-->
|
||||
|
||||
|
||||
<!--<delete file="${aip-path}"/>-->
|
||||
</target>
|
||||
|
||||
<target name="add-ai-productinfo" description="Add product information to the aip file">
|
||||
<scriptdef name="generateguid" language="javascript">
|
||||
<attribute name="property" />
|
||||
<![CDATA[
|
||||
@ -78,8 +57,6 @@
|
||||
]]>
|
||||
</scriptdef>
|
||||
<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 -->
|
||||
<echo>Product Code: ${guid1}</echo>
|
||||
<echo>Product Version: ${app.version}</echo>
|
||||
@ -92,55 +69,36 @@
|
||||
<replaceregexp file="${aip-path}"
|
||||
match="ProductVersion" Value="\d+\.{1}\d+\.{1}\d+"
|
||||
replace="ProductVersion" Value="${app.version}" />
|
||||
|
||||
<!-- Use Advanced Installer to configure files to add -->
|
||||
<echo message="Adding files to installer..."/>
|
||||
</target>
|
||||
|
||||
<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}">
|
||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\bin"/>
|
||||
<arg line="/edit ${aip-path} /Add${file-or-folder} APPDIR ${theFile}" />
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\etc"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\gstreamer"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\harness"/>
|
||||
</exec>
|
||||
<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>-->
|
||||
|
||||
</target>
|
||||
|
||||
<target name="is-file-or-folder">
|
||||
<condition property="file-or-folder" value="File" else="Folder">
|
||||
<available file="${theFile}" type="file" />
|
||||
</condition>
|
||||
</target>
|
||||
|
||||
<target name="add-ai-shortcuts" description="Add shortcuts to the aip file">
|
||||
<echo message="Adding desktop/menu shortcuts..."/>
|
||||
<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"/>
|
||||
@ -148,7 +106,10 @@
|
||||
<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"/>
|
||||
</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..."/>
|
||||
<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"/>
|
||||
@ -162,14 +123,6 @@
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/build ${aip-path}"/>
|
||||
</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>
|
||||
|
||||
</project>
|
||||
|
24
build.xml
24
build.xml
@ -54,10 +54,6 @@
|
||||
</condition>
|
||||
<fail unless="jreFound" message="JRE_HOME must be set as an environment variable."/>
|
||||
</target>
|
||||
|
||||
<target name="getExternals" depends="findTSK">
|
||||
<antcall target="copyTSKLibsToRelease" />
|
||||
</target>
|
||||
|
||||
<target name="getJunit">
|
||||
<property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" />
|
||||
@ -87,9 +83,7 @@
|
||||
<copy todir="${zip-tmp}/${app.name}/jre">
|
||||
<fileset dir="${env.JRE_HOME}"/>
|
||||
</copy>
|
||||
|
||||
<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="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 -->
|
||||
<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 -->
|
||||
<zip destfile="${nbdist.dir}/${app.name}-${app.version}.zip">
|
||||
<zipfileset dir="${zip-tmp}/${app.name}"/>
|
||||
@ -133,7 +139,7 @@
|
||||
<property name="app.version" value="${DSTAMP}"/>
|
||||
</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"/>
|
||||
<sortsuitemodules unsortedmodules="${modules}" sortedmodulesproperty="modules.sorted"/>
|
||||
<property name="cluster" location="build/cluster"/>
|
||||
@ -231,9 +237,8 @@
|
||||
</exec>
|
||||
</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" >
|
||||
<unzip src="${nbdist.dir}/${app.name}-${app.version}.zip" dest="${nbdist.dir}/${app.name}-installer"/>
|
||||
</target>
|
||||
@ -241,7 +246,6 @@
|
||||
<target name="build-installer" depends="build-installer-dir" description="Builds Autopsy installer.">
|
||||
<antcall target="build-installer-${os.family}" />
|
||||
</target>
|
||||
|
||||
<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.E02" property="img-present-2"/>
|
||||
|
@ -23,9 +23,9 @@
|
||||
#
|
||||
# http://wiki.sleuthkit.org/index.php?title=Autopsy_3_Module_Versions
|
||||
#
|
||||
# The basic idea is that this script uses javadoc/jdiff to
|
||||
# compare the current state of the source code to the last
|
||||
# tag and identifies if APIs were removed, added, etc.
|
||||
# The basic idea is that this script uses javadoc/jdiff to
|
||||
# compare the current state of the source code to the last
|
||||
# tag and identifies if APIs were removed, added, etc.
|
||||
#
|
||||
# When run from the Autopsy build script, this script will:
|
||||
# - Clone Autopsy and checkout to the previous release tag
|
||||
@ -61,6 +61,12 @@ from shutil import move
|
||||
from tempfile import mkstemp
|
||||
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
|
||||
class Module:
|
||||
# Initialize it with a name, return code, and version numbers
|
||||
@ -218,11 +224,11 @@ def compare_xml(module, apiname_tag, apiname_cur):
|
||||
log.close()
|
||||
code = jdiff.returncode
|
||||
print("Compared XML for " + module.name)
|
||||
if code == 100:
|
||||
if code == NO_CHANGES:
|
||||
print(" No API changes")
|
||||
elif code == 101:
|
||||
elif code == COMPATIBLE:
|
||||
print(" API Changes are backwards compatible")
|
||||
elif code == 102:
|
||||
elif code == NON_COMPATIBLE:
|
||||
print(" API Changes are not backwards compatible")
|
||||
else:
|
||||
print(" *Error in XML, most likely an empty module")
|
||||
@ -564,18 +570,18 @@ def update_versions(modules, source):
|
||||
if manifest == None or project == None:
|
||||
print(" Error finding manifeset and project properties files")
|
||||
return
|
||||
if module.ret == 101:
|
||||
if module.ret == COMPATIBLE:
|
||||
versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]]
|
||||
set_specification(project, manifest, versions[0])
|
||||
set_implementation(manifest, versions[1])
|
||||
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]
|
||||
set_specification(project, manifest, versions[0])
|
||||
set_implementation(manifest, versions[1])
|
||||
set_release(manifest, versions[2])
|
||||
module.set_versions(versions)
|
||||
elif module.ret == 100:
|
||||
elif module.ret == NO_CHANGES:
|
||||
versions = [versions[0], versions[1] + 1, versions[2]]
|
||||
set_implementation(manifest, versions[1])
|
||||
module.set_versions(versions)
|
||||
@ -624,48 +630,40 @@ def print_version_updates(modules):
|
||||
f = open("gen_version.txt", "a")
|
||||
for module in modules:
|
||||
versions = module.versions
|
||||
if module.ret == 101:
|
||||
if module.ret == COMPATIBLE:
|
||||
output = (module.name + ":\n")
|
||||
output += (" Current Specification version:\t" + str(versions[0]) + "\n")
|
||||
output += (" Updated Specification version:\t" + str(versions[0].increment()) + "\n")
|
||||
output += ("\n")
|
||||
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
|
||||
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
|
||||
output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].increment()) + "\n")
|
||||
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
|
||||
output += ("\tRelease:\tNo Change.\n")
|
||||
output += ("\n")
|
||||
print(output)
|
||||
sys.stdout.flush()
|
||||
f.write(output)
|
||||
elif module.ret == 102:
|
||||
elif module.ret == NON_COMPATIBLE:
|
||||
output = (module.name + ":\n")
|
||||
output += (" Current Specification version:\t" + str(versions[0]) + "\n")
|
||||
output += (" Updated Specification version:\t" + str(versions[0].overflow()) + "\n")
|
||||
output += ("\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 += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].overflow()) + "\n")
|
||||
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
|
||||
output += ("\tRelease:\t" + str(versions[2]) + "\t->\t" + str(versions[2] + 1) + "\n")
|
||||
output += ("\n")
|
||||
print(output)
|
||||
sys.stdout.flush()
|
||||
f.write(output)
|
||||
elif module.ret == 1:
|
||||
elif module.ret == ERROR:
|
||||
output = (module.name + ":\n")
|
||||
output += (" *Unable to detect necessary changes\n")
|
||||
output += (" Current Specification version:\t" + str(versions[0]) + "\n")
|
||||
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
|
||||
output += (" Current Release version:\t\t" + str(versions[2]) + "\n")
|
||||
output += ("\t*Unable to detect necessary changes\n")
|
||||
output += ("\tSpecification:\t" + str(versions[0]) + "\n")
|
||||
output += ("\tImplementation:\t" + str(versions[1]) + "\n")
|
||||
output += ("\tRelease:\t\t" + str(versions[2]) + "\n")
|
||||
output += ("\n")
|
||||
print(output)
|
||||
f.write(output)
|
||||
sys.stdout.flush()
|
||||
elif module.ret == 100:
|
||||
elif module.ret == NO_CHANGES:
|
||||
output = (module.name + ":\n")
|
||||
if versions[1] is None:
|
||||
output += (" No Implementation version.\n")
|
||||
output += ("\tImplementation: None\n")
|
||||
else:
|
||||
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
|
||||
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
|
||||
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
|
||||
output += ("\n")
|
||||
print(output)
|
||||
sys.stdout.flush()
|
||||
@ -673,16 +671,13 @@ def print_version_updates(modules):
|
||||
elif module.ret is None:
|
||||
output = ("Added " + module.name + ":\n")
|
||||
if module.spec() != "1.0" and module.spec() != "0.0":
|
||||
output += (" Current Specification version:\t" + str(module.spec()) + "\n")
|
||||
output += (" Updated Specification version:\t1.0\n")
|
||||
output += ("\tSpecification:\t" + str(module.spec()) + "\t->\t" + "1.0\n")
|
||||
output += ("\n")
|
||||
if module.impl() != 1:
|
||||
output += (" Current Implementation version:\t" + str(module.impl()) + "\n")
|
||||
output += (" Updated Implementation version:\t1\n")
|
||||
output += ("\tImplementation:\t" + str(module.impl()) + "\t->\t" + "1\n")
|
||||
output += ("\n")
|
||||
if module.release() != 1 and module.release() != 0:
|
||||
output += (" Current Release version:\t\t" + str(module.release()) + "\n")
|
||||
output += (" Updated Release version:\t\t1\n")
|
||||
output += ("Release:\t\t" + str(module.release()) + "\t->\t" + "1\n")
|
||||
output += ("\n")
|
||||
print(output)
|
||||
sys.stdout.flush()
|
||||
|
Loading…
x
Reference in New Issue
Block a user