This commit is contained in:
jonathan millman 2013-09-05 09:57:42 -04:00
commit d88dfeb2e5
14 changed files with 2488 additions and 1909 deletions

View File

@ -28,7 +28,6 @@ import org.openide.modules.ModuleInstall;
import org.openide.windows.WindowManager; import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
/** /**
* Wrapper over Installers in packages in Core module This is the main * Wrapper over Installers in packages in Core module This is the main
@ -40,56 +39,6 @@ public class Installer extends ModuleInstall {
private static final Logger logger = Logger.getLogger(Installer.class.getName()); private static final Logger logger = Logger.getLogger(Installer.class.getName());
private volatile boolean javaFxInit = true; private volatile boolean javaFxInit = true;
static {
loadDynLibraries();
}
private static void loadDynLibraries() {
if (PlatformUtil.isWindowsOS()) {
try {
//on windows force loading ms crt dependencies first
//in case linker can't find them on some systems
//Note: if shipping with a different CRT version, this will only print a warning
//and try to use linker mechanism to find the correct versions of libs.
//We should update this if we officially switch to a new version of CRT/compiler
System.loadLibrary("msvcr100");
System.loadLibrary("msvcp100");
logger.log(Level.INFO, "MS CRT libraries loaded");
} catch (UnsatisfiedLinkError e) {
logger.log(Level.SEVERE, "Error loading ms crt libraries, ", e);
}
}
try {
System.loadLibrary("zlib");
logger.log(Level.INFO, "ZLIB library loaded loaded");
} catch (UnsatisfiedLinkError e) {
logger.log(Level.SEVERE, "Error loading ZLIB library, ", e);
}
try {
System.loadLibrary("libewf");
logger.log(Level.INFO, "EWF library loaded");
} catch (UnsatisfiedLinkError e) {
logger.log(Level.SEVERE, "Error loading EWF library, ", e);
}
/* We should rename the Windows dll, to remove the lib prefix.
*/
try {
String tskLibName = null;
if (PlatformUtil.isWindowsOS()) {
tskLibName = "libtsk_jni";
} else {
tskLibName = "tsk_jni";
}
System.loadLibrary(tskLibName);
logger.log(Level.INFO, "TSK_JNI library loaded");
} catch (UnsatisfiedLinkError e) {
logger.log(Level.SEVERE, "Error loading tsk_jni library", e);
}
}
public Installer() { public Installer() {
javaFxInit = true; javaFxInit = true;
packageInstallers = new ArrayList<ModuleInstall>(); packageInstallers = new ArrayList<ModuleInstall>();

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011-2013 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -34,7 +34,7 @@ import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JTextPane; import javax.swing.JTextPane;
import javax.swing.text.Document; import javax.swing.SwingWorker;
import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet; import javax.swing.text.html.StyleSheet;
import org.openide.nodes.Node; import org.openide.nodes.Node;
@ -56,6 +56,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
private static int currentPage = 1; private static int currentPage = 1;
private List<BlackboardArtifact> artifacts; private List<BlackboardArtifact> artifacts;
private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName()); private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName());
private final static String WAIT_TEXT = "Preparing display...";
/** Creates new form DataContentViewerArtifact */ /** Creates new form DataContentViewerArtifact */
public DataContentViewerArtifact() { public DataContentViewerArtifact() {
@ -204,12 +205,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
currentPage = currentPage+1; currentPage = currentPage+1;
setDataView(artifacts, currentPage); new DisplayTask(currentPage).execute();
}//GEN-LAST:event_nextPageButtonActionPerformed }//GEN-LAST:event_nextPageButtonActionPerformed
private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
currentPage = currentPage-1; currentPage = currentPage-1;
setDataView(artifacts, currentPage); new DisplayTask(currentPage).execute();
}//GEN-LAST:event_prevPageButtonActionPerformed }//GEN-LAST:event_prevPageButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
@ -243,18 +244,28 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
} }
try { try {
this.setDataView(content.getAllArtifacts(), 1); artifacts = content.getAllArtifacts();
} catch (TskException ex) { }
logger.log(Level.WARNING, "Couldn't get artifacts: ", ex); catch (TskException ex) {
logger.log(Level.WARNING, "Couldn't get artifacts", ex);
resetComponent();
return;
} }
// focus on a specific artifact if it is in the node // focus on a specific artifact if it is in the node
int index = 0;
BlackboardArtifact artifact = lookup.lookup(BlackboardArtifact.class); BlackboardArtifact artifact = lookup.lookup(BlackboardArtifact.class);
if (artifact != null) { if (artifact != null) {
this.setSelectedArtifact(artifact); index = artifacts.indexOf(artifact);
if (index == -1) {
index = 0;
} }
} }
// A little bit of cleverness here - add one since setDataView() is also passed page numbers.
new DisplayTask(index + 1).execute();
}
@Override @Override
public String getTitle() { public String getTitle() {
return "Result View"; return "Result View";
@ -279,7 +290,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
public void resetComponent() { public void resetComponent() {
// clear / reset the fields // clear / reset the fields
currentPage = 1; currentPage = 1;
this.artifacts = new ArrayList<BlackboardArtifact>(); this.artifacts = new ArrayList<>();
currentPageLabel.setText(""); currentPageLabel.setText("");
totalPageLabel.setText(""); totalPageLabel.setText("");
outputViewPane.setText(""); outputViewPane.setText("");
@ -311,12 +322,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
return false; return false;
} }
ArtifactStringContent artifact = node.getLookup().lookup(ArtifactStringContent.class);
Content content = node.getLookup().lookup(Content.class); Content content = node.getLookup().lookup(Content.class);
if(artifact != null) {
return true;
}
if(content != null) { if(content != null) {
try { try {
long size = content.getAllArtifactsCount(); long size = content.getAllArtifactsCount();
@ -380,15 +386,14 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
* @param artifacts List of artifacts that could be displayed * @param artifacts List of artifacts that could be displayed
* @param offset Index into the list for the artifact to display * @param offset Index into the list for the artifact to display
*/ */
private void setDataView(List<BlackboardArtifact> artifacts, int offset) { private void setDataView(int offset) {
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
outputViewPane.setText(WAIT_TEXT);
if(artifacts.isEmpty()){ if(artifacts.isEmpty()){
resetComponent(); resetComponent();
return; return;
} }
this.artifacts = artifacts;
StringContent artifactString = new ArtifactStringContent(artifacts.get(offset-1)); StringContent artifactString = new ArtifactStringContent(artifacts.get(offset-1));
String text = artifactString.getString(); String text = artifactString.getString();
@ -408,14 +413,17 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
this.setCursor(null); this.setCursor(null);
} }
/** private class DisplayTask extends SwingWorker<Integer, Void> {
* Set the displayed artifact to the specified one. final int pageIndex;
* @param artifact Artifact to display
*/ DisplayTask(final int pageIndex) {
private void setSelectedArtifact(BlackboardArtifact artifact) { this.pageIndex = pageIndex;
if(artifacts.contains(artifact)) { }
int index = artifacts.indexOf(artifact);
setDataView(artifacts, index+1); @Override
public Integer doInBackground() {
setDataView(pageIndex);
return 0;
} }
} }
} }

View File

@ -16,6 +16,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>

View File

@ -43,7 +43,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
private String[] IMAGES; // use javafx supported private String[] IMAGES; // use javafx supported
private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; private static final String[] VIDEOS = new String[]{".swf", ".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"};
private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"};
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
@ -65,7 +65,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
initComponents(); initComponents();
videoPanel = new MediaViewVideoPanel(); // get the right panel for our platform
videoPanel = MediaViewVideoPanel.createVideoPanel();
imagePanel = new MediaViewImagePanel(); imagePanel = new MediaViewImagePanel();
videoPanelInited = videoPanel.isInited(); videoPanelInited = videoPanel.isInited();
imagePanelInited = imagePanel.isInited(); imagePanelInited = imagePanel.isInited();
@ -75,14 +77,14 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
} }
private void customizeComponents() { private void customizeComponents() {
logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
//initialize supported image types //initialize supported image types
//TODO use mime-types instead once we have support //TODO use mime-types instead once we have support
String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes(); String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes();
IMAGES = new String[fxSupportedImagesSuffixes.length]; IMAGES = new String[fxSupportedImagesSuffixes.length];
//logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) { for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) {
String suffix = fxSupportedImagesSuffixes[i]; String suffix = fxSupportedImagesSuffixes[i];
logger.log(Level.INFO, "suffix: " + suffix); //logger.log(Level.INFO, "suffix: " + suffix);
IMAGES[i] = "." + suffix; IMAGES[i] = "." + suffix;
} }
@ -110,6 +112,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
@Override @Override
public void setNode(Node selectedNode) { public void setNode(Node selectedNode) {
try {
if (selectedNode == null) { if (selectedNode == null) {
resetComponent(); resetComponent();
return; return;
@ -123,11 +126,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
if (file.equals(lastFile)) { if (file.equals(lastFile)) {
return; //prevent from loading twice if setNode() called mult. times return; //prevent from loading twice if setNode() called mult. times
} else {
lastFile = file;
} }
videoPanel.reset(); resetComponent();
final Dimension dims = DataContentViewerMedia.this.getSize(); final Dimension dims = DataContentViewerMedia.this.getSize();
@ -139,6 +140,10 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
videoPanel.setupVideo(file, dims); videoPanel.setupVideo(file, dims);
switchPanels(true); switchPanels(true);
} }
lastFile = file;
} 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 @Override
public void resetComponent() { public void resetComponent() {
lastFile = null;
videoPanel.reset(); videoPanel.reset();
// @@@ Seems like we should also reset the image viewer...
lastFile = null;
} }
@Override @Override
public boolean isSupported(Node node) { public boolean isSupported(Node node) {
if (node == null) { if (node == null) {

View 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;
// }
// }
}

View 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();
}
});
}
}
}

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -19,164 +19,94 @@
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Image; import java.util.Arrays;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.BoxLayout; import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.gstreamer.ClockTime;
import org.gstreamer.Gst;
import org.gstreamer.GstException;
import org.gstreamer.State;
import org.gstreamer.StateChangeReturn;
import org.gstreamer.elements.PlayBin2;
import org.gstreamer.elements.RGBDataSink;
import org.gstreamer.swing.VideoComponent;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/** /**
* Video viewer part of the Media View layered pane * Video viewer part of the Media View layered pane.
* Uses different engines depending on platform.
*/ */
@ServiceProviders(value = { public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture {
@ServiceProvider(service = FrameCapture.class)
})
public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture {
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
private boolean gstInited;
private static final long MIN_FRAME_INTERVAL_MILLIS = 500; // 64 bit architectures
private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000; private static final String[] ARCH64 = new String[]{"amd64", "x86_64"};
private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file.";
//playback // 32 bit architectures
private long durationMillis = 0; private static final String[] ARCH32 = new String[]{"x86"};
private VideoProgressWorker videoProgressWorker;
private int totalHours, totalMinutes, totalSeconds; // A Gstreamer implementation of MediaViewVideoPanel
private volatile PlayBin2 gstPlaybin2; private static GstVideoPanel gstVideoPanel = null;
private VideoComponent gstVideoComponent;
private boolean autoTracking = false; // true if the slider is moving automatically // A JavaFX implmentation of MediaViewVideoPanel
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player private static FXVideoPanel fxVideoPanel = null;
private AbstractFile currentFile;
private Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
/** /**
* Creates new form MediaViewVideoPanel * Factory Method to create a MediaViewVideoPanel.
*
* Implementation is dependent on the architecture of the JVM.
*
* @return a MediaViewVideoPanel instance.
*/ */
public MediaViewVideoPanel() { public static MediaViewVideoPanel createVideoPanel() {
initComponents(); if (is64BitJVM()) {
customizeComponents(); 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();
}
} }
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;
}
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 * Is the JVM architecture 64 bit?
* progressSlider.setValue() to avoid a different thread *
* changing playbin while stateChanged() is processing * @return
*/ */
@Override private static boolean is64BitJVM() {
public void stateChanged(ChangeEvent e) { String arch = System.getProperty("os.arch");
int time = progressSlider.getValue(); return Arrays.asList(ARCH64).contains(arch);
synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) {
State orig = gstPlaybin2.getState();
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.setState(orig);
}
}
}
});
} }
private boolean initGst() { /**
try { * Get a GStreamer video player implementation.
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); *
Gst.init(); * @return a GstVideoPanel
gstInited = true; */
} catch (GstException e) { private static MediaViewVideoPanel getGstImpl() {
gstInited = false; if (gstVideoPanel == null) {
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e); gstVideoPanel = new GstVideoPanel();
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. " }
+ " Video and audio viewing will be disabled. ", return gstVideoPanel;
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; /**
* Get a JavaFX video player implementation.
*
* @return a FXVideoPanel
*/
private static MediaViewVideoPanel getFXImpl() {
if (fxVideoPanel == null) {
fxVideoPanel = new FXVideoPanel();
} }
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. * Initialize all the necessary vars to play a video/audio file.
@ -184,588 +114,5 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
* @param file video file to play * @param file video file to play
* @param dims dimension of the parent window * @param dims dimension of the parent window
*/ */
void setupVideo(final AbstractFile file, final Dimension dims) { abstract void setupVideo(final AbstractFile file, final Dimension dims);
infoLabel.setText("");
currentFile = file;
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
if (deleted) {
infoLabel.setText("Playback of deleted videos is not supported, use an external player.");
videoPanel.removeAll();
pauseButton.setEnabled(false);
progressSlider.setEnabled(false);
return;
}
String path = "";
try {
path = file.getUniquePath();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Cannot get unique path of video file");
}
infoLabel.setText(path);
infoLabel.setToolTipText(path);
pauseButton.setEnabled(true);
progressSlider.setEnabled(true);
java.io.File ioFile = getJFile(file);
gstVideoComponent = new VideoComponent();
synchronized (playbinLock) {
if (gstPlaybin2 != null) {
gstPlaybin2.dispose();
}
gstPlaybin2 = new PlayBin2("VideoPlayer");
gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(gstVideoComponent);
videoPanel.setVisible(true);
gstPlaybin2.setInputFile(ioFile);
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
}
}
void reset() {
// reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressLabel.setText("");
}
});
if (!isInited()) {
return;
}
synchronized (playbinLock) {
if (gstPlaybin2 != null) {
if (gstPlaybin2.isPlaying()) {
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
}
if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.getState().equals(State.NULL)) {
gstPlaybin2.dispose();
}
gstPlaybin2 = null;
}
gstVideoComponent = null;
}
// get rid of any existing videoProgressWorker thread
if (videoProgressWorker != null) {
videoProgressWorker.cancel(true);
videoProgressWorker = null;
}
currentFile = null;
}
private java.io.File getJFile(AbstractFile file) {
// Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory();
String name = file.getName();
int extStart = name.lastIndexOf(".");
String ext = "";
if (extStart != -1) {
ext = name.substring(extStart, name.length()).toLowerCase();
}
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
java.io.File tempFile = new java.io.File(tempPath);
return tempFile;
}
/**
* @param file a video file from which to capture frames
* @param numFrames the number of frames to capture. These frames will be
* captured at successive intervals given by durationOfVideo/numFrames. If
* this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one
* frame will be captured and returned.
* @return a List of VideoFrames representing the captured frames.
*/
@Override
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
List<VideoFrame> frames = new ArrayList<>();
Object lock = new Object();
FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock);
if (!isInited()) {
return frames;
}
// throw exception if this file is known to be problematic
if (badVideoFiles.contains(file.getName())) {
throw new Exception("Cannot capture frames from this file (" + file.getName() + ").");
}
// set up a PlayBin2 object
RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener);
PlayBin2 playbin = new PlayBin2("VideoFrameCapture");
playbin.setInputFile(file);
playbin.setVideoSink(videoSink);
// this is necessary to get a valid duration value
StateChangeReturn ret = playbin.play();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to play while obtaining duration.");
}
ret = playbin.pause();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to pause while obtaining duration.");
}
playbin.getState();
// get the duration of the video
TimeUnit unit = TimeUnit.MILLISECONDS;
long myDurationMillis = playbin.queryDuration(unit);
if (myDurationMillis <= 0) {
return frames;
}
// calculate the number of frames to capture
int numFramesToGet = numFrames;
long frameInterval = myDurationMillis / numFrames;
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
numFramesToGet = 1;
}
// for each timeStamp, grap a frame
for (int i = 0; i < numFramesToGet; ++i) {
long timeStamp = i * frameInterval;
ret = playbin.pause();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to pause while capturing a frame.");
}
playbin.getState();
//System.out.println("Seeking to " + timeStamp + "milliseconds.");
if (!playbin.seek(timeStamp, unit)) {
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase());
}
ret = playbin.play();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to play while capturing a frame.");
}
// wait for FrameCaptureRGBListener to finish
synchronized(lock) {
try {
lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
} catch (InterruptedException e) {
logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e);
}
}
Image image = rgbListener.getImage();
ret = playbin.stop();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to stop while capturing a frame.");
}
if (image == null) {
logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName());
badVideoFiles.add(file.getName());
break;
}
frames.add(new VideoFrame(image, timeStamp));
}
return frames;
}
private class FrameCaptureRGBListener implements RGBDataSink.Listener {
public FrameCaptureRGBListener(Object waiter) {
this.waiter = waiter;
}
private BufferedImage bi;
private final Object waiter;
@Override
public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
synchronized (waiter) {
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
waiter.notify();
}
}
public Image getImage() {
synchronized (waiter) {
Image image = bi;
bi = null;
return image;
}
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
videoPanel = new javax.swing.JPanel();
controlPanel = new javax.swing.JPanel();
pauseButton = new javax.swing.JButton();
progressSlider = new javax.swing.JSlider();
progressLabel = new javax.swing.JLabel();
infoLabel = new javax.swing.JLabel();
javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
videoPanel.setLayout(videoPanelLayout);
videoPanelLayout.setHorizontalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
videoPanelLayout.setVerticalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 188, Short.MAX_VALUE)
);
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
pauseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pauseButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
controlPanel.setLayout(controlPanelLayout);
controlPanelLayout.setHorizontalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(infoLabel)
.addGap(0, 0, Short.MAX_VALUE))
);
controlPanelLayout.setVerticalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pauseButton)
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(progressLabel, javax.swing.GroupLayout.Alignment.TRAILING))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(infoLabel))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(videoPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
synchronized (playbinLock) {
State state = gstPlaybin2.getState();
if (state.equals(State.PLAYING)) {
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
pauseButton.setText("");
// Is this call necessary considering we just called gstPlaybin2.pause()?
if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
} else if (state.equals(State.PAUSED)) {
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
pauseButton.setText("||");
// Is this call necessary considering we just called gstPlaybin2.play()?
if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
} else if (state.equals(State.READY)) {
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute();
em.getExtractedBytes();
}
}
}//GEN-LAST:event_pauseButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel controlPanel;
private javax.swing.JLabel infoLabel;
private javax.swing.JButton pauseButton;
private javax.swing.JLabel progressLabel;
private javax.swing.JSlider progressSlider;
private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables
private class VideoProgressWorker extends SwingWorker<Object, Object> {
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d ";
private long millisElapsed = 0;
private final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50;
private boolean hadError = false;
private boolean isPlayBinReady() {
synchronized (playbinLock) {
return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
}
}
private void resetVideo() throws Exception {
synchronized (playbinLock) {
if (gstPlaybin2 != null) {
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
// ready to be played again
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
gstPlaybin2.getState(); //NEW
}
}
pauseButton.setText("");
progressSlider.setValue(0);
String durationStr = String.format(durationFormat, 0, 0, 0,
totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr);
}
/**
* @return true while millisElapsed is greater than END_TIME_MARGIN_MS
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
*/
private boolean hasNotEnded() {
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
}
@Override
protected Object doInBackground() throws Exception {
// enable the slider
progressSlider.setEnabled(true);
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
ClockTime pos = null;
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
synchronized (playbinLock) {
pos = gstPlaybin2.queryPosition();
}
millisElapsed = pos.toMillis();
// pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000;
elapsedHours = (int) secondsElapsed / 3600;
secondsElapsed -= elapsedHours * 3600;
elapsedMinutes = (int) secondsElapsed / 60;
secondsElapsed -= elapsedMinutes * 60;
elapsedSeconds = (int) secondsElapsed;
String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds,
totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr);
autoTracking = true;
progressSlider.setValue((int) millisElapsed);
autoTracking = false;
try {
Thread.sleep(INTER_FRAME_PERIOD_MS);
} catch (InterruptedException ex) {
break;
}
}
// disable the slider
progressSlider.setEnabled(false);
resetVideo();
return null;
}
} //end class progress worker
/* Thread that extracts and plays a file */
private class ExtractMedia extends SwingWorker<Object, Void> {
private ProgressHandle progress;
boolean success = false;
private AbstractFile sFile;
private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
this.sFile = sFile;
this.jFile = jFile;
}
public long getExtractedBytes() {
return extractedBytes;
}
@Override
protected Object doInBackground() throws Exception {
success = false;
progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() {
@Override
public boolean cancel() {
return ExtractMedia.this.cancel(true);
}
});
progressLabel.setText("Buffering... ");
progress.start();
progress.switchToDeterminate(100);
try {
extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex);
}
success = true;
return null;
}
/* clean up or start the worker threads */
@Override
protected void done() {
try {
super.get(); //block and get all exceptions thrown while doInBackground()
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled.");
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Media buffering was interrupted.");
} catch (Exception ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex);
} finally {
progress.finish();
if (!this.isCancelled()) {
playMedia();
}
}
}
void playMedia() {
if (jFile == null || !jFile.exists()) {
progressLabel.setText("Error buffering file");
return;
}
ClockTime dur = null;
synchronized (playbinLock) {
// must play, then pause and get state to get duration.
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.getState();
dur = gstPlaybin2.queryDuration();
}
duration = dur.toString();
durationMillis = dur.toMillis();
// pick out the total hours, minutes, seconds
long durationSeconds = (int) durationMillis / 1000;
totalHours = (int) durationSeconds / 3600;
durationSeconds -= totalHours * 3600;
totalMinutes = (int) durationSeconds / 60;
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0);
synchronized (playbinLock) {
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
}
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
}
});
}
}
} }

View File

@ -83,13 +83,22 @@ abstract class AbstractKeywordSearchPerformer extends javax.swing.JPanel impleme
@Override @Override
public void search() { public void search() {
boolean isRunning = IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault());
if (filesIndexed == 0) { if (filesIndexed == 0) {
KeywordSearchUtil.displayDialog("Keyword Search Error", "No files are indexed, please index an image before searching", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); if (isRunning) {
KeywordSearchUtil.displayDialog("Keyword Search Error", "<html>No files are in index yet. <br />"
+ "Try again later. Index is updated every " + KeywordSearchSettings.getUpdateFrequency().getTime() + " minutes.</html>", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
}
else {
KeywordSearchUtil.displayDialog("Keyword Search Error", "<html>No files were indexed.<br />"
+ "Re-ingest the image with the Keyword Search Module enabled. </html>", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
}
return; return;
} }
//check if keyword search module ingest is running (indexing, etc) //check if keyword search module ingest is running (indexing, etc)
if (IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault())) { if (isRunning) {
if (KeywordSearchUtil.displayConfirmDialog("Keyword Search Ingest in Progress", if (KeywordSearchUtil.displayConfirmDialog("Keyword Search Ingest in Progress",
"<html>Keyword Search Ingest is currently running.<br />" "<html>Keyword Search Ingest is currently running.<br />"
+ "Not all files have been indexed and this search might yield incomplete results.<br />" + "Not all files have been indexed and this search might yield incomplete results.<br />"

View File

@ -43,7 +43,7 @@ import org.apache.solr.client.solrj.SolrServerException;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
/** /**
* Keyword search toolbar which allows to search for single terms or phrases * Keyword search toolbar (in upper right, by default) which allows to search for single terms or phrases
* *
* The toolbar uses a different font from the rest of the application, Monospaced 14, * The toolbar uses a different font from the rest of the application, Monospaced 14,
* due to the necessity to find a font that displays both Arabic and Asian fonts at an acceptable size. * due to the necessity to find a font that displays both Arabic and Asian fonts at an acceptable size.

View File

@ -1,12 +1,22 @@
---------------- VERSION Current (development) -------------- ---------------- VERSION Current (development) --------------
New features: New features:
- 64-bit support (JavaFX for video)
- Multi-select
- different sized thumbnails
- Custom tags persist accross runs of the app
- RegRipper is run on each hive and raw output is available.
Improvements: Improvements:
- EXIF module uses only signatures
- File size view does not show unalloc files
- Tagged files in report show more data
- Updated test scripts
Bugfixes: Bugfixes:
- Several -> Didn't keep good track in this file.

View File

@ -3,72 +3,51 @@
<!-- Need a way to specify TSK Debug versus Release --> <!-- Need a way to specify TSK Debug versus Release -->
<property name="TSK_BUILD_TYPE">Release</property> <property name="TSK_BUILD_TYPE">Release</property>
<target name="copyTSKLibsToRelease">
<property environment="env"/>
<condition property="ewfFound">
<isset property="env.LIBEWF_HOME"/>
</condition>
<fail unless="ewfFound" message="LIBEWF_HOME must be set as an environment variable."/>
<copy file="${env.TSK_HOME}/win32/${TSK_BUILD_TYPE}/libtsk_jni.dll" tofile="${basedir}/Core/release/modules/lib/libtsk_jni.dll"/> <target name="build-installer-windows" depends="init-advanced-installer"
<copy file="${env.LIBEWF_HOME}/msvscpp/Release/libewf.dll" tofile="${basedir}/Core/release/modules/lib/libewf.dll"/> description="Makes an installer from the opened ZIP file">
<copy file="${env.LIBEWF_HOME}/msvscpp/Release/zlib.dll" tofile="${basedir}/Core/release/modules/lib/zlib.dll"/> <antcall target="run-advanced-installer" />
<!--<delete dir="${nbdist.dir}/${app.name}-installer"/>-->
<delete dir="${nbdist.dir}/installer_${app.name}-cache"/>
<move file="${nbdist.dir}/installer_${app.name}-SetupFiles/installer_${app.name}.msi" tofile="${nbdist.dir}/installer_${app.name}-${app.version}.msi" />
</target> </target>
<target name="copyExternalLibsToZip"> <target name="init-advanced-installer" depends="autoAIPath,inputAIPath"
description="Find AdvancedInstaller executable.">
<!-- Find CRT version we linked against from libtsk_jni manifest --> <fail unless="ai-exe-path" message="Could not locate Advanced Installer."/>
<!-- disable auto-detection for CRT100 <!-- Copy the template file to add details to -->
<property name="libtsk.manifest.path">${env.TSK_HOME}/win32/tsk_jni/${TSK_BUILD_TYPE}/libtsk_jni.dll.intermediate.manifest</property> <copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${nbdist.dir}/installer_${app.name}.aip" overwrite="true"/>
<loadfile property="libtsk.manifest" srcFile="${libtsk.manifest.path}" /> <property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/>
<propertyregex property="CRT.version" <property name="aip-path" value="${nbdist.dir}\installer_${app.name}.aip"/>
input="${libtsk.manifest}" <echo message="${ai-exe-path}" />
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>
<target name="autoAIPath" > <target name="autoAIPath" description="Attempt to find the AI path based on standard installation location">
<property name="AI.path">C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com</property> <property name="AI.path" value="C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com" />
<available file="${AI.path}" <available file="${AI.path}"
property="ai-exe-path" property="ai-exe-path"
value="${AI.path}"/> value="${AI.path}"/>
<echo message="${ai-exe-path}" />
</target> </target>
<target name="inputAIPath" unless="ai-exe-path"> <target name="inputAIPath" unless="ai-exe-path" description="Have the user input the path to the AI executable">
<input addProperty="ai-exe-path" <input addProperty="ai-exe-path"
message="Enter the location of AdvancedInstaller.com"/> message="Enter the location of AdvancedInstaller.com"/>
</target> </target>
<target name="run-advanced-installer" depends="autoAIPath,inputAIPath"> <target name="run-advanced-installer" depends="add-ai-productinfo,add-ai-files,add-ai-shortcuts,add-ai-env">
<fail unless="ai-exe-path" message="Could not locate Advanced Installer."/> <!-- Leaving this commented out bit for documentation purposes. Not sure what its supposed to do. -->
<!-- Copy the template file to add details to --> <!-- Need to find a way to deal with beta version -->
<copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${nbdist.dir}/installer_${app.name}.aip" overwrite="true"/> <!--<echo message="Setting ${app.name} version to ${app.version}..."/>
<exec executable="${ai-exe-path}">
<arg line="/edit ${aip-path} /SetVersion ${app.version}"/>
</exec>-->
<!--<delete file="${aip-path}"/>-->
</target>
<target name="add-ai-productinfo" description="Add product information to the aip file">
<scriptdef name="generateguid" language="javascript"> <scriptdef name="generateguid" language="javascript">
<attribute name="property" /> <attribute name="property" />
<![CDATA[ <![CDATA[
@ -78,8 +57,6 @@
]]> ]]>
</scriptdef> </scriptdef>
<generateguid property="guid1" /> <generateguid property="guid1" />
<property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/>
<property name="aip-path" value="${nbdist.dir}\installer_${app.name}.aip"/>
<!-- automatically replace version name and productcode in the .aip file --> <!-- automatically replace version name and productcode in the .aip file -->
<echo>Product Code: ${guid1}</echo> <echo>Product Code: ${guid1}</echo>
<echo>Product Version: ${app.version}</echo> <echo>Product Version: ${app.version}</echo>
@ -92,55 +69,36 @@
<replaceregexp file="${aip-path}" <replaceregexp file="${aip-path}"
match="ProductVersion&quot; Value=&quot;\d+\.{1}\d+\.{1}\d+" match="ProductVersion&quot; Value=&quot;\d+\.{1}\d+\.{1}\d+"
replace="ProductVersion&quot; Value=&quot;${app.version}" /> replace="ProductVersion&quot; Value=&quot;${app.version}" />
</target>
<!-- Use Advanced Installer to configure files to add --> <target name="add-ai-files" description="Add the files in the installation path to the installer file">
<echo message="Adding files to installer..."/> <foreach target="add-file-or-dir" param="theFile" inheritall="true" inheritrefs="true">
<exec executable="${ai-exe-path}"> <path>
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\bin"/> <fileset dir="${inst-path}">
</exec> <include name="*" />
<exec executable="${ai-exe-path}"> </fileset>
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\etc"/> <dirset dir="${inst-path}">
</exec> <include name="*"/>
<exec executable="${ai-exe-path}"> </dirset>
<arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\gstreamer"/> </path>
</exec> </foreach>
<exec executable="${ai-exe-path}"> </target>
<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 --> <target name="add-file-or-dir" depends="is-file-or-folder">
<!--<echo message="Setting ${app.name} version to ${app.version}..."/> <echo message="${ai-exe-path}" />
<echo message="Adding ${file-or-folder} to installer: ${theFile}"/>
<exec executable="${ai-exe-path}"> <exec executable="${ai-exe-path}">
<arg line="/edit ${aip-path} /SetVersion ${app.version}"/> <arg line="/edit ${aip-path} /Add${file-or-folder} APPDIR ${theFile}" />
</exec>--> </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..."/> <echo message="Adding desktop/menu shortcuts..."/>
<exec executable="${ai-exe-path}"> <exec executable="${ai-exe-path}">
<arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir DesktopFolder -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/> <arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir DesktopFolder -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/>
@ -148,7 +106,10 @@
<exec executable="${ai-exe-path}"> <exec executable="${ai-exe-path}">
<arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir SHORTCUTDIR -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/> <arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir SHORTCUTDIR -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/>
</exec> </exec>
</target>
<!-- TODO: does this always need to be done, or just for 64 bit files? -->
<target name="add-ai-env" description="Add the enviornment variables to the aip file">
<echo message="Setting environment variables..."/> <echo message="Setting environment variables..."/>
<exec executable="${ai-exe-path}"> <exec executable="${ai-exe-path}">
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/> <arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/>
@ -162,14 +123,6 @@
<exec executable="${ai-exe-path}"> <exec executable="${ai-exe-path}">
<arg line="/build ${aip-path}"/> <arg line="/build ${aip-path}"/>
</exec> </exec>
<!--<delete file="${aip-path}"/>-->
</target> </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> </project>

View File

@ -55,10 +55,6 @@
<fail unless="jreFound" message="JRE_HOME must be set as an environment variable."/> <fail unless="jreFound" message="JRE_HOME must be set as an environment variable."/>
</target> </target>
<target name="getExternals" depends="findTSK">
<antcall target="copyTSKLibsToRelease" />
</target>
<target name="getJunit"> <target name="getJunit">
<property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" /> <property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" />
<unzip src="${junit.dir}/junit.zip" dest="${nbplatform.active.dir}"/> <unzip src="${junit.dir}/junit.zip" dest="${nbplatform.active.dir}"/>
@ -87,9 +83,7 @@
<copy todir="${zip-tmp}/${app.name}/jre"> <copy todir="${zip-tmp}/${app.name}/jre">
<fileset dir="${env.JRE_HOME}"/> <fileset dir="${env.JRE_HOME}"/>
</copy> </copy>
<copy file="${basedir}/branding_${app.name}/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/> <copy file="${basedir}/branding_${app.name}/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/>
<antcall target="copyExternalLibsToZip"></antcall>
<property name="app.property.file" value="${zip-tmp}/${app.name}/etc/${app.name}.conf" /> <property name="app.property.file" value="${zip-tmp}/${app.name}/etc/${app.name}.conf" />
<property name="jvm.options" value="&quot;--branding ${app.name} -J-Xms24m -J-Xmx512m -J-XX:MaxPermSize=128M -J-Xverify:none&quot;" /> <property name="jvm.options" value="&quot;--branding ${app.name} -J-Xms24m -J-Xmx512m -J-XX:MaxPermSize=128M -J-Xverify:none&quot;" />
@ -101,6 +95,18 @@
<!-- workaround for ant escaping : and = when setting properties --> <!-- workaround for ant escaping : and = when setting properties -->
<replace file="${app.property.file}" token="@JVM_OPTIONS" value="${jvm.options}" /> <replace file="${app.property.file}" token="@JVM_OPTIONS" value="${jvm.options}" />
<!-- We want to remove the dlls in autopsy/modules/lib because they will
shadow the files in the autopsy/modules/lib/ARCHITECTURE folder in the JAR.
These files are legacy from when we used to copy the dlls to this location.
This check should do away in the future. Added Sept '13-->
<delete failonerror="false">
<fileset dir="${zip-tmp}/${app.name}/autopsy/modules/lib">
<include name="libtsk_jni.dll" />
<include name="libewf.dll" />
<include name="zlib.dll" />
</fileset>
</delete>
<!-- step (4) zip again, but with the version numbers in the dir --> <!-- step (4) zip again, but with the version numbers in the dir -->
<zip destfile="${nbdist.dir}/${app.name}-${app.version}.zip"> <zip destfile="${nbdist.dir}/${app.name}-${app.version}.zip">
<zipfileset dir="${zip-tmp}/${app.name}"/> <zipfileset dir="${zip-tmp}/${app.name}"/>
@ -133,7 +139,7 @@
<property name="app.version" value="${DSTAMP}"/> <property name="app.version" value="${DSTAMP}"/>
</target> </target>
<target name="-init" depends="-taskdefs,-convert-old-project,getExternals,getProps,getJunit"> <target name="-init" depends="-taskdefs,-convert-old-project,getProps,getJunit">
<convertclusterpath from="${cluster.path.evaluated}" to="cluster.path.final" id="cluster.path.id"/> <convertclusterpath from="${cluster.path.evaluated}" to="cluster.path.final" id="cluster.path.id"/>
<sortsuitemodules unsortedmodules="${modules}" sortedmodulesproperty="modules.sorted"/> <sortsuitemodules unsortedmodules="${modules}" sortedmodulesproperty="modules.sorted"/>
<property name="cluster" location="build/cluster"/> <property name="cluster" location="build/cluster"/>
@ -231,9 +237,8 @@
</exec> </exec>
</target> </target>
<target name="versioning-script" depends="check-release"/> <!--, versioning-script-if-release, versioning-script-if-not-release"/>--> <target name="versioning-script" depends="check-release, versioning-script-if-release, versioning-script-if-not-release"/>
<!-- opens the zip file into a folder in dist -->
<target name="build-installer-dir" depends="getProps, versioning-script, build-zip" description="Builds Autopsy with branding and all dependencies" > <target name="build-installer-dir" depends="getProps, versioning-script, build-zip" description="Builds Autopsy with branding and all dependencies" >
<unzip src="${nbdist.dir}/${app.name}-${app.version}.zip" dest="${nbdist.dir}/${app.name}-installer"/> <unzip src="${nbdist.dir}/${app.name}-${app.version}.zip" dest="${nbdist.dir}/${app.name}-installer"/>
</target> </target>
@ -241,7 +246,6 @@
<target name="build-installer" depends="build-installer-dir" description="Builds Autopsy installer."> <target name="build-installer" depends="build-installer-dir" description="Builds Autopsy installer.">
<antcall target="build-installer-${os.family}" /> <antcall target="build-installer-${os.family}" />
</target> </target>
<target name="test-download-imgs" description="Get test images and store them in the path represented by the test-input variable."> <target name="test-download-imgs" description="Get test images and store them in the path represented by the test-input variable.">
<available file="${test-input}/nps-2008-jean.E01" property="img-present-1"/> <available file="${test-input}/nps-2008-jean.E01" property="img-present-1"/>
<available file="${test-input}/nps-2008-jean.E02" property="img-present-2"/> <available file="${test-input}/nps-2008-jean.E02" property="img-present-2"/>

View File

@ -61,6 +61,12 @@ from shutil import move
from tempfile import mkstemp from tempfile import mkstemp
from xml.dom.minidom import parse, parseString from xml.dom.minidom import parse, parseString
# Jdiff return codes. Described in more detail further on
NO_CHANGES = 100
COMPATIBLE = 101
NON_COMPATIBLE = 102
ERROR = 1
# An Autopsy module object # An Autopsy module object
class Module: class Module:
# Initialize it with a name, return code, and version numbers # Initialize it with a name, return code, and version numbers
@ -218,11 +224,11 @@ def compare_xml(module, apiname_tag, apiname_cur):
log.close() log.close()
code = jdiff.returncode code = jdiff.returncode
print("Compared XML for " + module.name) print("Compared XML for " + module.name)
if code == 100: if code == NO_CHANGES:
print(" No API changes") print(" No API changes")
elif code == 101: elif code == COMPATIBLE:
print(" API Changes are backwards compatible") print(" API Changes are backwards compatible")
elif code == 102: elif code == NON_COMPATIBLE:
print(" API Changes are not backwards compatible") print(" API Changes are not backwards compatible")
else: else:
print(" *Error in XML, most likely an empty module") print(" *Error in XML, most likely an empty module")
@ -564,18 +570,18 @@ def update_versions(modules, source):
if manifest == None or project == None: if manifest == None or project == None:
print(" Error finding manifeset and project properties files") print(" Error finding manifeset and project properties files")
return return
if module.ret == 101: if module.ret == COMPATIBLE:
versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]] versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]]
set_specification(project, manifest, versions[0]) set_specification(project, manifest, versions[0])
set_implementation(manifest, versions[1]) set_implementation(manifest, versions[1])
module.set_versions(versions) module.set_versions(versions)
elif module.ret == 102: elif module.ret == NON_COMPATIBLE:
versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1] versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1]
set_specification(project, manifest, versions[0]) set_specification(project, manifest, versions[0])
set_implementation(manifest, versions[1]) set_implementation(manifest, versions[1])
set_release(manifest, versions[2]) set_release(manifest, versions[2])
module.set_versions(versions) module.set_versions(versions)
elif module.ret == 100: elif module.ret == NO_CHANGES:
versions = [versions[0], versions[1] + 1, versions[2]] versions = [versions[0], versions[1] + 1, versions[2]]
set_implementation(manifest, versions[1]) set_implementation(manifest, versions[1])
module.set_versions(versions) module.set_versions(versions)
@ -624,48 +630,40 @@ def print_version_updates(modules):
f = open("gen_version.txt", "a") f = open("gen_version.txt", "a")
for module in modules: for module in modules:
versions = module.versions versions = module.versions
if module.ret == 101: if module.ret == COMPATIBLE:
output = (module.name + ":\n") output = (module.name + ":\n")
output += (" Current Specification version:\t" + str(versions[0]) + "\n") output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].increment()) + "\n")
output += (" Updated Specification version:\t" + str(versions[0].increment()) + "\n") output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
output += ("\n") output += ("\tRelease:\tNo Change.\n")
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
output += ("\n") output += ("\n")
print(output) print(output)
sys.stdout.flush() sys.stdout.flush()
f.write(output) f.write(output)
elif module.ret == 102: elif module.ret == NON_COMPATIBLE:
output = (module.name + ":\n") output = (module.name + ":\n")
output += (" Current Specification version:\t" + str(versions[0]) + "\n") output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].overflow()) + "\n")
output += (" Updated Specification version:\t" + str(versions[0].overflow()) + "\n") output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
output += ("\n") output += ("\tRelease:\t" + str(versions[2]) + "\t->\t" + str(versions[2] + 1) + "\n")
output += (" Current Implementation version:\t" + str(versions[1]) + "\n")
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
output += ("\n")
output += (" Current Release version:\t\t" + str(versions[2]) + "\n")
output += (" Updated Release version:\t\t" + str(versions[2] + 1) + "\n")
output += ("\n") output += ("\n")
print(output) print(output)
sys.stdout.flush() sys.stdout.flush()
f.write(output) f.write(output)
elif module.ret == 1: elif module.ret == ERROR:
output = (module.name + ":\n") output = (module.name + ":\n")
output += (" *Unable to detect necessary changes\n") output += ("\t*Unable to detect necessary changes\n")
output += (" Current Specification version:\t" + str(versions[0]) + "\n") output += ("\tSpecification:\t" + str(versions[0]) + "\n")
output += (" Current Implementation version:\t" + str(versions[1]) + "\n") output += ("\tImplementation:\t" + str(versions[1]) + "\n")
output += (" Current Release version:\t\t" + str(versions[2]) + "\n") output += ("\tRelease:\t\t" + str(versions[2]) + "\n")
output += ("\n") output += ("\n")
print(output) print(output)
f.write(output) f.write(output)
sys.stdout.flush() sys.stdout.flush()
elif module.ret == 100: elif module.ret == NO_CHANGES:
output = (module.name + ":\n") output = (module.name + ":\n")
if versions[1] is None: if versions[1] is None:
output += (" No Implementation version.\n") output += ("\tImplementation: None\n")
else: else:
output += (" Current Implementation version:\t" + str(versions[1]) + "\n") output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n")
output += ("\n") output += ("\n")
print(output) print(output)
sys.stdout.flush() sys.stdout.flush()
@ -673,16 +671,13 @@ def print_version_updates(modules):
elif module.ret is None: elif module.ret is None:
output = ("Added " + module.name + ":\n") output = ("Added " + module.name + ":\n")
if module.spec() != "1.0" and module.spec() != "0.0": if module.spec() != "1.0" and module.spec() != "0.0":
output += (" Current Specification version:\t" + str(module.spec()) + "\n") output += ("\tSpecification:\t" + str(module.spec()) + "\t->\t" + "1.0\n")
output += (" Updated Specification version:\t1.0\n")
output += ("\n") output += ("\n")
if module.impl() != 1: if module.impl() != 1:
output += (" Current Implementation version:\t" + str(module.impl()) + "\n") output += ("\tImplementation:\t" + str(module.impl()) + "\t->\t" + "1\n")
output += (" Updated Implementation version:\t1\n")
output += ("\n") output += ("\n")
if module.release() != 1 and module.release() != 0: if module.release() != 1 and module.release() != 0:
output += (" Current Release version:\t\t" + str(module.release()) + "\n") output += ("Release:\t\t" + str(module.release()) + "\t->\t" + "1\n")
output += (" Updated Release version:\t\t1\n")
output += ("\n") output += ("\n")
print(output) print(output)
sys.stdout.flush() sys.stdout.flush()