From dde1ed5b4e274606f47ea31dd99e2a76b4bb5a86 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 30 Jul 2013 16:04:36 -0400 Subject: [PATCH 01/21] Initial change from gstreamer to vlc --- .../corecomponents/MediaViewVideoPanel.java | 412 ++++++------------ 1 file changed, 130 insertions(+), 282 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index da1ca268d5..0828249e7f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -18,18 +18,16 @@ */ package org.sleuthkit.autopsy.corecomponents; +import com.sun.jna.Native; 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; @@ -40,13 +38,7 @@ 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; @@ -60,6 +52,12 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import uk.co.caprica.vlcj.binding.LibVlc; +import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent; +import uk.co.caprica.vlcj.player.MediaPlayer; +import uk.co.caprica.vlcj.player.MediaPlayerEventAdapter; +import uk.co.caprica.vlcj.player.MediaPlayerFactory; +import uk.co.caprica.vlcj.runtime.RuntimeUtil; /** * Video viewer part of the Media View layered pane @@ -71,6 +69,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); private boolean gstInited; + private boolean vlcInited; 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."; @@ -79,10 +78,13 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt private VideoProgressWorker videoProgressWorker; private int totalHours, totalMinutes, totalSeconds; private volatile PlayBin2 gstPlaybin2; + private MediaPlayer vlcMediaPlayer; private VideoComponent gstVideoComponent; + private EmbeddedMediaPlayerComponent vlcVideoComponent; 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 boolean isFileLoaded; private Set badVideoFiles = Collections.synchronizedSet(new HashSet()); /** @@ -109,73 +111,68 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt return videoPanel; } - public VideoComponent getVideoComponent() { - return gstVideoComponent; + public EmbeddedMediaPlayerComponent getVideoComponent() { +// return gstVideoComponent; + return vlcVideoComponent; } public boolean isInited() { - return gstInited; +// return gstInited; + return vlcInited; } private void customizeComponents() { - if (!initGst()) { + if (!initVlc()) { 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); - } + 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(); + if (vlcMediaPlayer != null && !autoTracking) { + boolean wasPlaying = !vlcMediaPlayer.isPlaying(); + vlcMediaPlayer.setPause(true); + vlcMediaPlayer.setTime(time); + vlcMediaPlayer.setPause(wasPlaying); } } - }); + }); + } + + private void pause() { + if (vlcMediaPlayer != null) { + vlcMediaPlayer.setPause(true); + pauseButton.setText("►"); + } + } + + private void unPause() { + if (vlcMediaPlayer != null) { + vlcMediaPlayer.setPause(false); + pauseButton.setText("||"); + } } - private boolean initGst() { + private boolean initVlc() { 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; + Native.loadLibrary(RuntimeUtil.getLibVlcLibraryName(), LibVlc.class); + vlcInited = true; + } catch (UnsatisfiedLinkError e) { + logger.log(Level.SEVERE, "Error initalizing vlc for audio/video viewing and extraction capabilities", e); + MessageNotifyUtil.Notify.error("Error initializing vlc for audio/video viewing and frame extraction capabilities. " + + " Video and audio viewing will be disabled. ", e.getMessage()); + vlcInited = false; } - - return true; + return vlcInited; } /** @@ -187,6 +184,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt void setupVideo(final AbstractFile file, final Dimension dims) { infoLabel.setText(""); currentFile = file; + isFileLoaded = false; 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."); @@ -195,7 +193,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt progressSlider.setEnabled(false); return; } - + String path = ""; try { path = file.getUniquePath(); @@ -208,35 +206,19 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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); - } - } - + + vlcVideoComponent = new EmbeddedMediaPlayerComponent(); + vlcMediaPlayer = vlcVideoComponent.getMediaPlayer(); + vlcMediaPlayer.setPlaySubItems(true); + vlcMediaPlayer.addMediaPlayerEventListener(new VlcMediaPlayerEventListener()); + videoPanel.removeAll(); + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(vlcVideoComponent); + videoPanel.setVisible(true); + logger.log(Level.INFO, "Created media player."); } void reset() { - // reset the progress label text on the event dispatch thread SwingUtilities.invokeLater(new Runnable() { @Override @@ -249,26 +231,18 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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; + if (vlcMediaPlayer != null) { + if (vlcMediaPlayer.isPlaying()) { + vlcMediaPlayer.stop(); } - gstVideoComponent = null; + vlcMediaPlayer.release(); + vlcMediaPlayer = null; + logger.log(Level.INFO, "Released media player"); + } + if (vlcVideoComponent != null) { + vlcVideoComponent.release(true); + vlcVideoComponent = null; + logger.log(Level.INFO, "Released video component"); } // get rid of any existing videoProgressWorker thread @@ -307,43 +281,24 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt public List captureFrames(java.io.File file, int numFrames) throws Exception { List 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."); + MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory(); + MediaPlayer mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer(); + boolean mediaPrepared = mediaPlayer.prepareMedia(file.getAbsolutePath()); + if (!mediaPrepared) { + return frames; } - 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); + long myDurationMillis = mediaPlayer.getLength(); if (myDurationMillis <= 0) { return frames; } @@ -359,82 +314,16 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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(); + mediaPlayer.setTime(timeStamp); + mediaPlayer.pause(); - //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."); - } + Image snapShot = mediaPlayer.getSnapshot(); - // 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)); + frames.add(new VideoFrame(snapShot, 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. @@ -518,39 +407,16 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt }// //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(); - } + if (!isFileLoaded) { + // First time play button is pressed + logger.log(Level.INFO, "Loading media file"); + ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); + em.execute(); + em.getExtractedBytes(); + } else if (vlcMediaPlayer.isPlaying()) { + this.pause(); + } else { + this.unPause(); } }//GEN-LAST:event_pauseButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables @@ -570,26 +436,14 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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 boolean isMediaPlayerReady() { + return vlcMediaPlayer != 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 - } + logger.log(Level.INFO, "Resetting video."); + if (vlcMediaPlayer != null) { + vlcMediaPlayer.stop(); } pauseButton.setText("►"); progressSlider.setValue(0); @@ -606,7 +460,8 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt * the reported duration of the video. */ private boolean hasNotEnded() { - return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; + boolean ended = (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; + return ended; } @Override @@ -616,13 +471,9 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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(); + while (hasNotEnded() && isMediaPlayerReady() && !isCancelled()) { + + millisElapsed = vlcMediaPlayer.getTime(); // pick out the elapsed hours, minutes, seconds long secondsElapsed = millisElapsed / 1000; @@ -711,7 +562,9 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); } finally { progress.finish(); + isFileLoaded = true; if (!this.isCancelled()) { + logger.log(Level.INFO, "Loaded media file"); playMedia(); } } @@ -722,24 +575,15 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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(); + + boolean mediaPrepared = vlcMediaPlayer.prepareMedia(jFile.getAbsolutePath()); + if (mediaPrepared) { + vlcMediaPlayer.parseMedia(); + durationMillis = vlcMediaPlayer.getMediaMeta().getLength(); + logger.log(Level.INFO, "Media loaded correctly"); + } else { + progressLabel.setText(MEDIA_PLAYER_ERROR_STRING); } - duration = dur.toString(); - durationMillis = dur.toMillis(); // pick out the total hours, minutes, seconds long durationSeconds = (int) durationMillis / 1000; @@ -755,12 +599,8 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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); - } - } + logger.log(Level.INFO, "Starting the media..."); + vlcMediaPlayer.start(); pauseButton.setText("||"); videoProgressWorker = new VideoProgressWorker(); videoProgressWorker.execute(); @@ -768,4 +608,12 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt }); } } + + private class VlcMediaPlayerEventListener extends MediaPlayerEventAdapter { + + @Override + public void error(MediaPlayer mediaPlayer) { + logger.log(Level.INFO, "an Error occured."); + } + } } From 67548141b11968f6c81f68230af0591f93d697b4 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Wed, 31 Jul 2013 15:37:30 -0400 Subject: [PATCH 02/21] Revert "Rectified incorrect copyright notice update" This reverts commit 71b3b29f2655d312b1da0d6ebc289d786e21f464. --- Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index dbe63a4b64..780e14351d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -4,7 +4,7 @@ * * Copyright 2013 Basis Technology Corp. * - * Copyright 2012 42six Solutions. + * Copyright 2013 42six Solutions. * Contact: aebadirad 42six com * Project Contact/Architect: carrier sleuthkit org * From a18fdc33e7cd836c28f7d0e3e2e3a8090e85df0d Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Wed, 31 Jul 2013 15:59:02 -0400 Subject: [PATCH 03/21] Updated vlc video component use and fixed bugs --- .../DataContentViewerMedia.java | 515 +++++++++--------- .../corecomponents/MediaViewVideoPanel.java | 228 +++++--- 2 files changed, 399 insertions(+), 344 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index 1368598dee..1ec9eaeb8b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -1,256 +1,259 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2013 Basis Technology Corp. - * Contact: carrier sleuthkit 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.CardLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.util.Arrays; -import java.util.logging.Level; -import javax.imageio.ImageIO; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.SwingUtilities; -import org.openide.nodes.Node; -import org.openide.util.Exceptions; -import org.openide.util.lookup.ServiceProvider; -import org.openide.util.lookup.ServiceProviders; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; - -/** - * Media content viewer for videos, sounds and images. - */ -@ServiceProviders(value = { - @ServiceProvider(service = DataContentViewer.class, position = 5) -}) -public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { - - private String[] IMAGES; // use javafx supported - private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; - private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; - - private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); - - private AbstractFile lastFile; - //UI - private final MediaViewVideoPanel videoPanel; - private final MediaViewImagePanel imagePanel; - private boolean videoPanelInited; - private boolean imagePanelInited; - - private static final String IMAGE_VIEWER_LAYER = "IMAGE"; - private static final String VIDEO_VIEWER_LAYER = "VIDEO"; - - /** - * Creates new form DataContentViewerVideo - */ - public DataContentViewerMedia() { - - initComponents(); - - videoPanel = new MediaViewVideoPanel(); - imagePanel = new MediaViewImagePanel(); - videoPanelInited = videoPanel.isInited(); - imagePanelInited = imagePanel.isInited(); - - customizeComponents(); - logger.log(Level.INFO, "Created MediaView instance: " + this); - } - - private void customizeComponents() { - logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); - //initialize supported image types - //TODO use mime-types instead once we have support - String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes(); - IMAGES = new String[fxSupportedImagesSuffixes.length]; - for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) { - String suffix = fxSupportedImagesSuffixes[i]; - logger.log(Level.INFO, "suffix: " + suffix); - IMAGES[i] = "." + suffix; - } - - add(imagePanel, IMAGE_VIEWER_LAYER); - add(videoPanel, VIDEO_VIEWER_LAYER); - - switchPanels(false); - - } - - /** - * 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") - // //GEN-BEGIN:initComponents - private void initComponents() { - - setLayout(new java.awt.CardLayout()); - getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.AccessibleContext.accessibleDescription")); // NOI18N - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - // End of variables declaration//GEN-END:variables - - @Override - public void setNode(Node selectedNode) { - - if (selectedNode == null) { - videoPanel.reset(); - return; - } - - AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class); - if (file == null) { - return; - } - - if (file.equals(lastFile)) { - return; //prevent from loading twice if setNode() called mult. times - } else { - lastFile = file; - } - - videoPanel.reset(); - - final Dimension dims = DataContentViewerMedia.this.getSize(); - - if (imagePanelInited && containsExt(file.getName(), IMAGES)) { - imagePanel.showImageFx(file, dims); - this.switchPanels(false); - } else if (videoPanelInited - && (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) { - videoPanel.setupVideo(file, dims); - switchPanels(true); - } - } - - /** - * switch to visible video or image panel - * - * @param showVideo true if video panel, false if image panel - */ - private void switchPanels(boolean showVideo) { - CardLayout layout = (CardLayout)this.getLayout(); - if (showVideo) { - layout.show(this, VIDEO_VIEWER_LAYER); - } else { - layout.show(this, IMAGE_VIEWER_LAYER); - } - } - - @Override - public String getTitle() { - return "Media View"; - } - - @Override - public String getToolTip() { - return "Displays supported multimedia files (images, videos, audio)"; - } - - @Override - public DataContentViewer createInstance() { - return new DataContentViewerMedia(); - } - - @Override - public Component getComponent() { - return this; - } - - @Override - public void resetComponent() { - // we don't want this to do anything - // because we already reset on each selected node - } - - - - @Override - public boolean isSupported(Node node) { - - if (node == null) { - return false; - } - - AbstractFile file = node.getLookup().lookup(AbstractFile.class); - if (file == null) { - return false; - } - - - if (file.getSize() == 0) { - return false; - } - - String name = file.getName().toLowerCase(); - - if (imagePanelInited && containsExt(name, IMAGES)) { - return true; - } //for gstreamer formats, check if initialized first, then - //support audio formats, and video formats - else if (videoPanelInited && videoPanel.isInited() - && (containsExt(name, AUDIOS) - || (containsExt(name, VIDEOS)))) { - return true; - } - - return false; - } - - @Override - public int isPreferred(Node node, boolean isSupported) { - if (isSupported) { - //special case, check if deleted video, then do not make it preferred - AbstractFile file = node.getLookup().lookup(AbstractFile.class); - if (file == null) { - return 0; - } - String name = file.getName().toLowerCase(); - - boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC); - if (containsExt(name, VIDEOS) && deleted) { - return 0; - } else { - return 7; - } - } else { - return 0; - } - } - - private static boolean containsExt(String name, String[] exts) { - int extStart = name.lastIndexOf("."); - String ext = ""; - if (extStart != -1) { - ext = name.substring(extStart, name.length()).toLowerCase(); - } - return Arrays.asList(exts).contains(ext); - } -} - - - - - - - - - +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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.CardLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.logging.Level; +import javax.imageio.ImageIO; +import org.sleuthkit.autopsy.coreutils.Logger; +import javax.swing.SwingUtilities; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; + +/** + * Media content viewer for videos, sounds and images. + */ +@ServiceProviders(value = { + @ServiceProvider(service = DataContentViewer.class, position = 5) +}) +public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { + + private String[] IMAGES; // use javafx supported + private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; + private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; + + private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); + + private AbstractFile lastFile; + //UI + private final MediaViewVideoPanel videoPanel; + private final MediaViewImagePanel imagePanel; + private boolean videoPanelInited; + private boolean imagePanelInited; + + private static final String IMAGE_VIEWER_LAYER = "IMAGE"; + private static final String VIDEO_VIEWER_LAYER = "VIDEO"; + + /** + * Creates new form DataContentViewerVideo + */ + public DataContentViewerMedia() { + + initComponents(); + + videoPanel = new MediaViewVideoPanel(); + imagePanel = new MediaViewImagePanel(); + videoPanelInited = videoPanel.isInited(); + imagePanelInited = imagePanel.isInited(); + + customizeComponents(); + logger.log(Level.INFO, "Created MediaView instance: " + this); + } + + private void customizeComponents() { + logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); + //initialize supported image types + //TODO use mime-types instead once we have support + String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes(); + IMAGES = new String[fxSupportedImagesSuffixes.length]; + for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) { + String suffix = fxSupportedImagesSuffixes[i]; + logger.log(Level.INFO, "suffix: " + suffix); + IMAGES[i] = "." + suffix; + } + + add(imagePanel, IMAGE_VIEWER_LAYER); + add(videoPanel, VIDEO_VIEWER_LAYER); + + switchPanels(false); + + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setLayout(new java.awt.CardLayout()); + getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.AccessibleContext.accessibleDescription")); // NOI18N + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + + @Override + public void setNode(Node selectedNode) { + try { + if (selectedNode == null) { + videoPanel.reset(); + return; + } + + AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class); + if (file == null) { + return; + } + + if (file.equals(lastFile)) { + return; //prevent from loading twice if setNode() called mult. times + } else { + lastFile = file; + } + + videoPanel.reset(); + + final Dimension dims = DataContentViewerMedia.this.getSize(); + + if (imagePanelInited && containsExt(file.getName(), IMAGES)) { + imagePanel.showImageFx(file, dims); + this.switchPanels(false); + } else if (videoPanelInited + && (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) { + videoPanel.setupVideo(file, dims); + switchPanels(true); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Exception while setting node", e); + } + } + + /** + * switch to visible video or image panel + * + * @param showVideo true if video panel, false if image panel + */ + private void switchPanels(boolean showVideo) { + CardLayout layout = (CardLayout)this.getLayout(); + if (showVideo) { + layout.show(this, VIDEO_VIEWER_LAYER); + } else { + layout.show(this, IMAGE_VIEWER_LAYER); + } + } + + @Override + public String getTitle() { + return "Media View"; + } + + @Override + public String getToolTip() { + return "Displays supported multimedia files (images, videos, audio)"; + } + + @Override + public DataContentViewer createInstance() { + return new DataContentViewerMedia(); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void resetComponent() { + // we don't want this to do anything + // because we already reset on each selected node + } + + + + @Override + public boolean isSupported(Node node) { + + if (node == null) { + return false; + } + + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file == null) { + return false; + } + + + if (file.getSize() == 0) { + return false; + } + + String name = file.getName().toLowerCase(); + + if (imagePanelInited && containsExt(name, IMAGES)) { + return true; + } //for gstreamer formats, check if initialized first, then + //support audio formats, and video formats + else if (videoPanelInited && videoPanel.isInited() + && (containsExt(name, AUDIOS) + || (containsExt(name, VIDEOS)))) { + return true; + } + + return false; + } + + @Override + public int isPreferred(Node node, boolean isSupported) { + if (isSupported) { + //special case, check if deleted video, then do not make it preferred + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file == null) { + return 0; + } + String name = file.getName().toLowerCase(); + + boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC); + if (containsExt(name, VIDEOS) && deleted) { + return 0; + } else { + return 7; + } + } else { + return 0; + } + } + + private static boolean containsExt(String name, String[] exts) { + int extStart = name.lastIndexOf("."); + String ext = ""; + if (extStart != -1) { + ext = name.substring(extStart, name.length()).toLowerCase(); + } + return Arrays.asList(exts).contains(ext); + } +} + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index 0828249e7f..df41785c5e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -38,8 +38,6 @@ import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import org.gstreamer.elements.PlayBin2; -import org.gstreamer.swing.VideoComponent; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.util.Cancellable; @@ -68,23 +66,21 @@ import uk.co.caprica.vlcj.runtime.RuntimeUtil; public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); - private boolean gstInited; private boolean vlcInited; 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."; + private static final int POS_FACTOR = 10000; //playback private long durationMillis = 0; private VideoProgressWorker videoProgressWorker; private int totalHours, totalMinutes, totalSeconds; - private volatile PlayBin2 gstPlaybin2; private MediaPlayer vlcMediaPlayer; - private VideoComponent gstVideoComponent; private EmbeddedMediaPlayerComponent vlcVideoComponent; 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 boolean isFileLoaded; + private java.io.File currentVideoFile; + private boolean replay; private Set badVideoFiles = Collections.synchronizedSet(new HashSet()); /** @@ -112,12 +108,10 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } public EmbeddedMediaPlayerComponent getVideoComponent() { -// return gstVideoComponent; return vlcVideoComponent; } public boolean isInited() { -// return gstInited; return vlcInited; } @@ -137,14 +131,15 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt */ @Override public void stateChanged(ChangeEvent e) { - int time = progressSlider.getValue(); - if (vlcMediaPlayer != null && !autoTracking) { - boolean wasPlaying = !vlcMediaPlayer.isPlaying(); - vlcMediaPlayer.setPause(true); - vlcMediaPlayer.setTime(time); - vlcMediaPlayer.setPause(wasPlaying); + if (vlcMediaPlayer != null && !autoTracking) { + float positionValue = progressSlider.getValue() / (float) POS_FACTOR; + // Avoid end of file freeze-up + if (positionValue > 0.99f) { + positionValue = 0.99f; } + vlcMediaPlayer.setPosition(positionValue); } + } }); } @@ -161,7 +156,28 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt pauseButton.setText("||"); } } + + /** + * Reset the video for replaying the current file. + * + * Called when finished Event is fired from MediaPlayer. + */ + private void finished() { + if (videoProgressWorker != null) { + videoProgressWorker.cancel(true); + videoProgressWorker = null; + } + + logger.log(Level.INFO, "Resetting media"); + replay = true; + } + /** + * Load the VLC library dll using JNA. + * + * @return true, if the library was loaded correctly. + * false, otherwise. + */ private boolean initVlc() { try { Native.loadLibrary(RuntimeUtil.getLibVlcLibraryName(), LibVlc.class); @@ -184,7 +200,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt void setupVideo(final AbstractFile file, final Dimension dims) { infoLabel.setText(""); currentFile = file; - isFileLoaded = false; + replay = false; 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."); @@ -204,8 +220,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt infoLabel.setToolTipText(path); pauseButton.setEnabled(true); progressSlider.setEnabled(true); - - java.io.File ioFile = getJFile(file); vlcVideoComponent = new EmbeddedMediaPlayerComponent(); vlcMediaPlayer = vlcVideoComponent.getMediaPlayer(); @@ -216,6 +230,9 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt videoPanel.add(vlcVideoComponent); videoPanel.setVisible(true); logger.log(Level.INFO, "Created media player."); + + ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); + em.execute(); } void reset() { @@ -230,7 +247,13 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt if (!isInited()) { return; } - + + // get rid of any existing videoProgressWorker thread + if (videoProgressWorker != null) { + videoProgressWorker.cancel(true); + videoProgressWorker = null; + } + if (vlcMediaPlayer != null) { if (vlcMediaPlayer.isPlaying()) { vlcMediaPlayer.stop(); @@ -245,13 +268,8 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt logger.log(Level.INFO, "Released video component"); } - // get rid of any existing videoProgressWorker thread - if (videoProgressWorker != null) { - videoProgressWorker.cancel(true); - videoProgressWorker = null; - } - currentFile = null; + currentVideoFile = null; } private java.io.File getJFile(AbstractFile file) { @@ -268,6 +286,45 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt java.io.File tempFile = new java.io.File(tempPath); return tempFile; } + + void playMedia() { + logger.log(Level.INFO, "In play media"); + if (currentVideoFile == null || !currentVideoFile.exists()) { + progressLabel.setText("Error buffering file"); + return; + } + + boolean mediaPrepared = vlcMediaPlayer.prepareMedia(currentVideoFile.getAbsolutePath()); + if (mediaPrepared) { + vlcMediaPlayer.parseMedia(); + durationMillis = vlcMediaPlayer.getMediaMeta().getLength(); + logger.log(Level.INFO, "Media loaded correctly"); + } else { + progressLabel.setText(MEDIA_PLAYER_ERROR_STRING); + } + + // 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(POS_FACTOR); + progressSlider.setMinimum(0); + + logger.log(Level.INFO, "Starting the media..."); + vlcMediaPlayer.start(); + pauseButton.setText("||"); + videoProgressWorker = new VideoProgressWorker(); + videoProgressWorker.execute(); + } + }); + } /** * @param file a video file from which to capture frames @@ -359,6 +416,8 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } }); + progressSlider.setSnapToTicks(true); + 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 @@ -407,15 +466,16 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt }// //GEN-END:initComponents private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed - if (!isFileLoaded) { - // First time play button is pressed - logger.log(Level.INFO, "Loading media file"); - ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); - em.execute(); - em.getExtractedBytes(); + if (replay) { + // File has completed playing. Play button now replays + logger.log(Level.INFO, "Replaying video."); + replay = false; + playMedia(); } else if (vlcMediaPlayer.isPlaying()) { + logger.log(Level.INFO, "Pausing."); this.pause(); } else { + logger.log(Level.INFO, "Playing"); this.unPause(); } }//GEN-LAST:event_pauseButtonActionPerformed @@ -428,10 +488,23 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables + void releaseVlcComponents() { + if (vlcMediaPlayer != null) { + vlcMediaPlayer.release(); + vlcMediaPlayer = null; + } + + if (vlcVideoComponent != null) { + vlcVideoComponent.release(); + vlcVideoComponent = null; + } + } + private class VideoProgressWorker extends SwingWorker { private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; private long millisElapsed = 0; + private float currentPosition = 0.0f; private final long INTER_FRAME_PERIOD_MS = 20; private final long END_TIME_MARGIN_MS = 50; private boolean hadError = false; @@ -441,28 +514,23 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } private void resetVideo() throws Exception { - logger.log(Level.INFO, "Resetting video."); - if (vlcMediaPlayer != null) { - vlcMediaPlayer.stop(); - } 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() { - boolean ended = (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; - return ended; - } +// /** +// * @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() { +// boolean ended = (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; +// return ended; +// } @Override protected Object doInBackground() throws Exception { @@ -471,10 +539,11 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt progressSlider.setEnabled(true); int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; - while (hasNotEnded() && isMediaPlayerReady() && !isCancelled()) { + while (isMediaPlayerReady() && !isCancelled()) { millisElapsed = vlcMediaPlayer.getTime(); - + currentPosition = vlcMediaPlayer.getPosition(); + // pick out the elapsed hours, minutes, seconds long secondsElapsed = millisElapsed / 1000; elapsedHours = (int) secondsElapsed / 3600; @@ -489,7 +558,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt progressLabel.setText(durationStr); autoTracking = true; - progressSlider.setValue((int) millisElapsed); + progressSlider.setValue((int) (currentPosition * POS_FACTOR)); autoTracking = false; try { @@ -562,58 +631,41 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); } finally { progress.finish(); - isFileLoaded = true; if (!this.isCancelled()) { logger.log(Level.INFO, "Loaded media file"); + currentVideoFile = jFile; playMedia(); } } } - - void playMedia() { - if (jFile == null || !jFile.exists()) { - progressLabel.setText("Error buffering file"); - return; - } - - boolean mediaPrepared = vlcMediaPlayer.prepareMedia(jFile.getAbsolutePath()); - if (mediaPrepared) { - vlcMediaPlayer.parseMedia(); - durationMillis = vlcMediaPlayer.getMediaMeta().getLength(); - logger.log(Level.INFO, "Media loaded correctly"); - } else { - progressLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } - - // 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); - - logger.log(Level.INFO, "Starting the media..."); - vlcMediaPlayer.start(); - pauseButton.setText("||"); - videoProgressWorker = new VideoProgressWorker(); - videoProgressWorker.execute(); - } - }); - } } private class VlcMediaPlayerEventListener extends MediaPlayerEventAdapter { + @Override + public void finished(MediaPlayer mediaPlayer) { + logger.log(Level.INFO, "Media Player finished playing the media"); + finished(); + } + @Override public void error(MediaPlayer mediaPlayer) { logger.log(Level.INFO, "an Error occured."); } + + @Override + public void mediaDurationChanged(MediaPlayer mediaPlayer, long newDuration) { + logger.log(Level.INFO, "DURATION CHANGED: " + newDuration); + } + + @Override + public void mediaFreed(MediaPlayer mediaPlayer) { + logger.log(Level.INFO, "Media was freed"); + } + + @Override + public void lengthChanged(MediaPlayer mediaPlayer, long newLength) { + logger.log(Level.INFO, "LENGTH CHANGED: " + newLength); + } } } From 5f823cee3098a196e5e230f5a7bb171cd7f705b8 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Wed, 31 Jul 2013 16:03:28 -0400 Subject: [PATCH 04/21] Revert "Revert "Rectified incorrect copyright notice update"" This reverts commit 67548141b11968f6c81f68230af0591f93d697b4. --- Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 780e14351d..dbe63a4b64 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -4,7 +4,7 @@ * * Copyright 2013 Basis Technology Corp. * - * Copyright 2013 42six Solutions. + * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com * Project Contact/Architect: carrier sleuthkit org * From ed75e9258b2c3501aa30c4633b98f8c6fde9f31c Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Thu, 1 Aug 2013 16:14:58 -0400 Subject: [PATCH 05/21] Bug fixes with vlc. Tested component with external modules. --- Core/nbproject/project.xml | 444 +++++++++--------- .../DataContentViewerMedia.java | 2 +- .../corecomponents/MediaViewVideoPanel.java | 265 ++++++----- 3 files changed, 366 insertions(+), 345 deletions(-) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 9b49c8e266..dc5a236e24 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -1,218 +1,226 @@ - - - org.netbeans.modules.apisupport.project - - - org.sleuthkit.autopsy.core - - - - org.netbeans.api.progress - - - - 1 - 1.28.1 - - - - org.netbeans.core - - - - 2 - - - - - org.netbeans.core.startup - - - - 1 - - - - - org.netbeans.modules.javahelp - - - - 1 - 2.27.1 - - - - org.netbeans.modules.options.api - - - - 1 - 1.26.1 - - - - org.netbeans.modules.settings - - - - 1 - 1.35.1 - - - - org.netbeans.spi.quicksearch - - - - 1.14.1 - - - - org.netbeans.swing.outline - - - - 1.20.1 - - - - org.netbeans.swing.plaf - - - - 1.25.1 - - - - org.netbeans.swing.tabcontrol - - - - 1.36.1 - - - - org.openide.actions - - - - 6.26.1 - - - - org.openide.awt - - - - 7.46.1 - - - - org.openide.dialogs - - - - 7.25.1 - - - - org.openide.explorer - - - - 6.45.1 - - - - org.openide.filesystems - - - - 7.62.1 - - - - org.openide.modules - - - - 7.32.1 - - - - org.openide.nodes - - - - 7.28.1 - - - - org.openide.text - - - - 6.49.1 - - - - org.openide.util - - - - 8.25.1 - - - - org.openide.util.lookup - - - - 8.15.1 - - - - org.openide.windows - - - - 6.55.1 - - - - org.sleuthkit.autopsy.corelibs - - - - 3 - 1.0 - - - - - org.sleuthkit.autopsy.casemodule - org.sleuthkit.autopsy.casemodule.services - org.sleuthkit.autopsy.core - org.sleuthkit.autopsy.corecomponentinterfaces - org.sleuthkit.autopsy.corecomponents - org.sleuthkit.autopsy.coreutils - org.sleuthkit.autopsy.datamodel - org.sleuthkit.autopsy.directorytree - org.sleuthkit.autopsy.filesearch - org.sleuthkit.autopsy.ingest - org.sleuthkit.autopsy.menuactions - org.sleuthkit.autopsy.report - org.sleuthkit.datamodel - - - ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar - release/modules/ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar - - - ext/Tsk_DataModel.jar - release/modules/ext/Tsk_DataModel.jar - - - - + + + org.netbeans.modules.apisupport.project + + + org.sleuthkit.autopsy.core + + + + org.netbeans.api.progress + + + + 1 + 1.28.1 + + + + org.netbeans.core + + + + 2 + + + + + org.netbeans.core.startup + + + + 1 + + + + + org.netbeans.modules.javahelp + + + + 1 + 2.27.1 + + + + org.netbeans.modules.options.api + + + + 1 + 1.26.1 + + + + org.netbeans.modules.settings + + + + 1 + 1.35.1 + + + + org.netbeans.spi.quicksearch + + + + 1.14.1 + + + + org.netbeans.swing.outline + + + + 1.20.1 + + + + org.netbeans.swing.plaf + + + + 1.25.1 + + + + org.netbeans.swing.tabcontrol + + + + 1.36.1 + + + + org.openide.actions + + + + 6.26.1 + + + + org.openide.awt + + + + 7.46.1 + + + + org.openide.dialogs + + + + 7.25.1 + + + + org.openide.explorer + + + + 6.45.1 + + + + org.openide.filesystems + + + + 7.62.1 + + + + org.openide.modules + + + + 7.32.1 + + + + org.openide.nodes + + + + 7.28.1 + + + + org.openide.text + + + + 6.49.1 + + + + org.openide.util + + + + 8.25.1 + + + + org.openide.util.lookup + + + + 8.15.1 + + + + org.openide.windows + + + + 6.55.1 + + + + org.sleuthkit.autopsy.corelibs + + + + 3 + 1.0 + + + + + org.sleuthkit.autopsy.casemodule + org.sleuthkit.autopsy.casemodule.services + org.sleuthkit.autopsy.core + org.sleuthkit.autopsy.corecomponentinterfaces + org.sleuthkit.autopsy.corecomponents + org.sleuthkit.autopsy.coreutils + org.sleuthkit.autopsy.datamodel + org.sleuthkit.autopsy.directorytree + org.sleuthkit.autopsy.filesearch + org.sleuthkit.autopsy.ingest + org.sleuthkit.autopsy.menuactions + org.sleuthkit.autopsy.report + org.sleuthkit.datamodel + + + ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar + release/modules/ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar + + + ext/Tsk_DataModel.jar + release/modules/ext/Tsk_DataModel.jar + + + ext/vlcj-2.4.1.jar + release/modules/ext/vlcj-2.4.1.jar + + + ext/jna-3.5.2.jar + release/modules/ext/jna-3.5.2.jar + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index 1ec9eaeb8b..17d9cd2f63 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -43,7 +43,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { private String[] IMAGES; // use javafx supported - private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; + private static final String[] VIDEOS = new String[]{".swf", ".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index df41785c5e..6d1c8857e9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -20,13 +20,10 @@ package org.sleuthkit.autopsy.corecomponents; import com.sun.jna.Native; import java.awt.Dimension; -import java.awt.Image; +import java.awt.image.BufferedImage; import java.io.IOException; 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.logging.Level; import javax.swing.BoxLayout; @@ -58,7 +55,7 @@ import uk.co.caprica.vlcj.player.MediaPlayerFactory; import uk.co.caprica.vlcj.runtime.RuntimeUtil; /** - * Video viewer part of the Media View layered pane + * Video viewer part of the Media View layered pane. */ @ServiceProviders(value = { @ServiceProvider(service = FrameCapture.class) @@ -66,23 +63,37 @@ import uk.co.caprica.vlcj.runtime.RuntimeUtil; public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); - private boolean vlcInited; + private boolean vlcInited; // Has the vlc library been initalized properly? private static final long MIN_FRAME_INTERVAL_MILLIS = 500; - private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000; + private static final long INTER_FRAME_PERIOD_MS = 20; // Time between frames. private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file."; - private static final int POS_FACTOR = 10000; - //playback + private static final int POS_FACTOR = 10000; // The number of ticks on the video progress bar + // Frame Capture Media Player Args + private static final String[] VLC_FRAME_CAPTURE_ARGS = { + "--intf", "dummy", // no interface + "--vout", "dummy", // we don't want video (output) + "--no-audio", // we don't want audio (decoding) + "--no-video-title-show", // nor the filename displayed + "--no-stats", // no stats + "--no-sub-autodetect-file", // we don't want subtitles + "--no-disable-screensaver", // we don't want interfaces + "--no-snapshot-preview" // no blending in dummy vout + }; + // Playback state information private long durationMillis = 0; - private VideoProgressWorker videoProgressWorker; private int totalHours, totalMinutes, totalSeconds; + private boolean autoTracking = false; // true if the slider is moving automatically + private boolean replay; + // VLC Components private MediaPlayer vlcMediaPlayer; private EmbeddedMediaPlayerComponent vlcVideoComponent; - private boolean autoTracking = false; // true if the slider is moving automatically + // Worker threads + private VideoProgressWorker videoProgressWorker; + private MediaPlayerThread mediaPlayerThread; + // Current media content representations private AbstractFile currentFile; private java.io.File currentVideoFile; - private boolean replay; - private Set badVideoFiles = Collections.synchronizedSet(new HashSet()); - + /** * Creates new form MediaViewVideoPanel */ @@ -124,11 +135,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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) { if (vlcMediaPlayer != null && !autoTracking) { @@ -167,8 +173,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt videoProgressWorker.cancel(true); videoProgressWorker = null; } - - logger.log(Level.INFO, "Resetting media"); + mediaPlayerThread = new MediaPlayerThread(); replay = true; } @@ -221,20 +226,27 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt pauseButton.setEnabled(true); progressSlider.setEnabled(true); + // Create and Configure MediaPlayer vlcVideoComponent = new EmbeddedMediaPlayerComponent(); vlcMediaPlayer = vlcVideoComponent.getMediaPlayer(); vlcMediaPlayer.setPlaySubItems(true); vlcMediaPlayer.addMediaPlayerEventListener(new VlcMediaPlayerEventListener()); + + // Configure VideoPanel videoPanel.removeAll(); videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.add(vlcVideoComponent); videoPanel.setVisible(true); - logger.log(Level.INFO, "Created media player."); + // Create Extraction and Playback Threads + mediaPlayerThread = new MediaPlayerThread(); ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); em.execute(); } + /** + * Cleanup all threads and VLC components and reset UI. + */ void reset() { // reset the progress label text on the event dispatch thread SwingUtilities.invokeLater(new Runnable() { @@ -248,29 +260,39 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt return; } + if (mediaPlayerThread != null) { + mediaPlayerThread.cancel(); + mediaPlayerThread = null; + } + // get rid of any existing videoProgressWorker thread if (videoProgressWorker != null) { videoProgressWorker.cancel(true); videoProgressWorker = null; } - + if (vlcMediaPlayer != null) { if (vlcMediaPlayer.isPlaying()) { vlcMediaPlayer.stop(); } vlcMediaPlayer.release(); vlcMediaPlayer = null; - logger.log(Level.INFO, "Released media player"); } if (vlcVideoComponent != null) { vlcVideoComponent.release(true); vlcVideoComponent = null; - logger.log(Level.INFO, "Released video component"); } currentFile = null; currentVideoFile = null; } + + /** + * Start the media player. + */ + void playMedia() { + SwingUtilities.invokeLater(mediaPlayerThread); + } private java.io.File getJFile(AbstractFile file) { // Get the temp folder path of the case @@ -286,45 +308,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt java.io.File tempFile = new java.io.File(tempPath); return tempFile; } - - void playMedia() { - logger.log(Level.INFO, "In play media"); - if (currentVideoFile == null || !currentVideoFile.exists()) { - progressLabel.setText("Error buffering file"); - return; - } - - boolean mediaPrepared = vlcMediaPlayer.prepareMedia(currentVideoFile.getAbsolutePath()); - if (mediaPrepared) { - vlcMediaPlayer.parseMedia(); - durationMillis = vlcMediaPlayer.getMediaMeta().getLength(); - logger.log(Level.INFO, "Media loaded correctly"); - } else { - progressLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } - - // 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(POS_FACTOR); - progressSlider.setMinimum(0); - - logger.log(Level.INFO, "Starting the media..."); - vlcMediaPlayer.start(); - pauseButton.setText("||"); - videoProgressWorker = new VideoProgressWorker(); - videoProgressWorker.execute(); - } - }); - } /** * @param file a video file from which to capture frames @@ -343,42 +326,52 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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() + ")."); - } - - MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory(); + // Create Media Player with no video output + MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory(VLC_FRAME_CAPTURE_ARGS); MediaPlayer mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer(); boolean mediaPrepared = mediaPlayer.prepareMedia(file.getAbsolutePath()); if (!mediaPrepared) { + logger.log(Level.WARNING, "Video file was not loaded properly."); return frames; } + // get the duration of the video - long myDurationMillis = mediaPlayer.getLength(); + mediaPlayer.parseMedia(); + long myDurationMillis = mediaPlayer.getMediaMeta().getLength(); if (myDurationMillis <= 0) { return frames; } // calculate the number of frames to capture int numFramesToGet = numFrames; - long frameInterval = myDurationMillis / numFrames; + long frameInterval = (myDurationMillis - INTER_FRAME_PERIOD_MS) / numFrames; if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { numFramesToGet = 1; } + mediaPlayer.start(); + mediaPlayer.setPause(true); + + BufferedImage snapShot; // for each timeStamp, grap a frame for (int i = 0; i < numFramesToGet; ++i) { - long timeStamp = i * frameInterval; + logger.log(Level.INFO, "Grabbing a frame..."); + long timeStamp = i * frameInterval + INTER_FRAME_PERIOD_MS; mediaPlayer.setTime(timeStamp); - mediaPlayer.pause(); + mediaPlayer.setPause(true); - Image snapShot = mediaPlayer.getSnapshot(); + snapShot = mediaPlayer.getSnapshot(); + if (snapShot == null) { + continue; + } frames.add(new VideoFrame(snapShot, timeStamp)); } + // cleanup media player + mediaPlayer.release(); + mediaPlayerFactory.release(); return frames; } @@ -467,15 +460,12 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed if (replay) { - // File has completed playing. Play button now replays - logger.log(Level.INFO, "Replaying video."); + // File has completed playing. Play button now replays media. replay = false; playMedia(); } else if (vlcMediaPlayer.isPlaying()) { - logger.log(Level.INFO, "Pausing."); this.pause(); } else { - logger.log(Level.INFO, "Playing"); this.unPause(); } }//GEN-LAST:event_pauseButtonActionPerformed @@ -488,31 +478,21 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables - void releaseVlcComponents() { - if (vlcMediaPlayer != null) { - vlcMediaPlayer.release(); - vlcMediaPlayer = null; - } - - if (vlcVideoComponent != null) { - vlcVideoComponent.release(); - vlcVideoComponent = null; - } - } - + /** + * Thread that updates the video progress bar and current time with information + * queried from the MediaPlayer. + */ private class VideoProgressWorker extends SwingWorker { private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; private long millisElapsed = 0; private float currentPosition = 0.0f; - private final long INTER_FRAME_PERIOD_MS = 20; - private final long END_TIME_MARGIN_MS = 50; - private boolean hadError = false; private boolean isMediaPlayerReady() { return vlcMediaPlayer != null; } + // TODO: Could be moved to finished() private void resetVideo() throws Exception { pauseButton.setText("►"); @@ -521,17 +501,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt 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() { -// boolean ended = (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; -// return ended; -// } - @Override protected Object doInBackground() throws Exception { @@ -540,7 +509,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; while (isMediaPlayerReady() && !isCancelled()) { - millisElapsed = vlcMediaPlayer.getTime(); currentPosition = vlcMediaPlayer.getPosition(); @@ -577,15 +545,16 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } } //end class progress worker - /* Thread that extracts and plays a file */ + /** + * 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 { 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) { @@ -614,6 +583,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } catch (IOException ex) { logger.log(Level.WARNING, "Error buffering file", ex); } + logger.log(Level.INFO, "Done buffering: " + jFile.getName()); success = true; return null; } @@ -632,7 +602,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } finally { progress.finish(); if (!this.isCancelled()) { - logger.log(Level.INFO, "Loaded media file"); + logger.log(Level.INFO, "ExtractMedia in done: " + jFile.getName()); currentVideoFile = jFile; playMedia(); } @@ -640,6 +610,63 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } } + /** + * Thread that is responsible for running the Media Player. + */ + private class MediaPlayerThread implements Runnable, Cancellable { + + private volatile boolean cancelled = false; + + /* Prepare and run the current media. */ + @Override + public void run() { + if (currentVideoFile == null || !currentVideoFile.exists()) { + progressLabel.setText("Error buffering file"); + return; + } + boolean mediaPrepared = vlcMediaPlayer.prepareMedia(currentVideoFile.getAbsolutePath()); + if (mediaPrepared) { + vlcMediaPlayer.parseMedia(); + durationMillis = vlcMediaPlayer.getMediaMeta().getLength(); + } else { + progressLabel.setText(MEDIA_PLAYER_ERROR_STRING); + } + + // 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; + + progressSlider.setMaximum(POS_FACTOR); + progressSlider.setMinimum(0); + if (!isCancelled()) { + vlcMediaPlayer.start(); + pauseButton.setText("||"); + videoProgressWorker = new VideoProgressWorker(); + videoProgressWorker.execute(); + } else { + logger.log(Level.INFO, "Media Playing cancelled."); + } + } + + @Override + public boolean cancel() { + cancelled = true; + return cancelled; + } + + private boolean isCancelled() { + return cancelled; + } + + } + + /** + * An Object that listens and handles events thrown by the VLC Media Player. + */ private class VlcMediaPlayerEventListener extends MediaPlayerEventAdapter { @Override @@ -650,22 +677,8 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt @Override public void error(MediaPlayer mediaPlayer) { - logger.log(Level.INFO, "an Error occured."); - } - - @Override - public void mediaDurationChanged(MediaPlayer mediaPlayer, long newDuration) { - logger.log(Level.INFO, "DURATION CHANGED: " + newDuration); - } - - @Override - public void mediaFreed(MediaPlayer mediaPlayer) { - logger.log(Level.INFO, "Media was freed"); - } - - @Override - public void lengthChanged(MediaPlayer mediaPlayer, long newLength) { - logger.log(Level.INFO, "LENGTH CHANGED: " + newLength); + logger.log(Level.WARNING, "A VLC error occured. Resetting the video panel."); + reset(); } } } From fe8257e1db6af44fbc8cc8fc47878237c833a4d5 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Mon, 5 Aug 2013 08:53:01 -0400 Subject: [PATCH 06/21] Reverted Video Panel back to vlc. --- .../corecomponents/MediaViewVideoPanel.java | 597 ++++++++++-------- 1 file changed, 342 insertions(+), 255 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index 6d1c8857e9..da1ca268d5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -18,13 +18,18 @@ */ package org.sleuthkit.autopsy.corecomponents; -import com.sun.jna.Native; 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; @@ -35,6 +40,14 @@ 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; @@ -47,15 +60,9 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; -import uk.co.caprica.vlcj.binding.LibVlc; -import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent; -import uk.co.caprica.vlcj.player.MediaPlayer; -import uk.co.caprica.vlcj.player.MediaPlayerEventAdapter; -import uk.co.caprica.vlcj.player.MediaPlayerFactory; -import uk.co.caprica.vlcj.runtime.RuntimeUtil; /** - * Video viewer part of the Media View layered pane. + * Video viewer part of the Media View layered pane */ @ServiceProviders(value = { @ServiceProvider(service = FrameCapture.class) @@ -63,37 +70,21 @@ import uk.co.caprica.vlcj.runtime.RuntimeUtil; public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); - private boolean vlcInited; // Has the vlc library been initalized properly? + private boolean gstInited; private static final long MIN_FRAME_INTERVAL_MILLIS = 500; - private static final long INTER_FRAME_PERIOD_MS = 20; // Time between frames. + private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000; private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file."; - private static final int POS_FACTOR = 10000; // The number of ticks on the video progress bar - // Frame Capture Media Player Args - private static final String[] VLC_FRAME_CAPTURE_ARGS = { - "--intf", "dummy", // no interface - "--vout", "dummy", // we don't want video (output) - "--no-audio", // we don't want audio (decoding) - "--no-video-title-show", // nor the filename displayed - "--no-stats", // no stats - "--no-sub-autodetect-file", // we don't want subtitles - "--no-disable-screensaver", // we don't want interfaces - "--no-snapshot-preview" // no blending in dummy vout - }; - // Playback state information + //playback private long durationMillis = 0; - private int totalHours, totalMinutes, totalSeconds; - private boolean autoTracking = false; // true if the slider is moving automatically - private boolean replay; - // VLC Components - private MediaPlayer vlcMediaPlayer; - private EmbeddedMediaPlayerComponent vlcVideoComponent; - // Worker threads private VideoProgressWorker videoProgressWorker; - private MediaPlayerThread mediaPlayerThread; - // Current media content representations + 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 java.io.File currentVideoFile; - + private Set badVideoFiles = Collections.synchronizedSet(new HashSet()); + /** * Creates new form MediaViewVideoPanel */ @@ -118,82 +109,73 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt return videoPanel; } - public EmbeddedMediaPlayerComponent getVideoComponent() { - return vlcVideoComponent; + public VideoComponent getVideoComponent() { + return gstVideoComponent; } public boolean isInited() { - return vlcInited; + return gstInited; } private void customizeComponents() { - if (!initVlc()) { + if (!initGst()) { return; } progressSlider.setEnabled(false); // disable slider; enable after user plays vid progressSlider.setValue(0); - progressSlider.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (vlcMediaPlayer != null && !autoTracking) { - float positionValue = progressSlider.getValue() / (float) POS_FACTOR; - // Avoid end of file freeze-up - if (positionValue > 0.99f) { - positionValue = 0.99f; + 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); + } } - vlcMediaPlayer.setPosition(positionValue); } - } - }); - } - - private void pause() { - if (vlcMediaPlayer != null) { - vlcMediaPlayer.setPause(true); - pauseButton.setText("►"); - } - } - - private void unPause() { - if (vlcMediaPlayer != null) { - vlcMediaPlayer.setPause(false); - pauseButton.setText("||"); - } - } - - /** - * Reset the video for replaying the current file. - * - * Called when finished Event is fired from MediaPlayer. - */ - private void finished() { - if (videoProgressWorker != null) { - videoProgressWorker.cancel(true); - videoProgressWorker = null; - } - mediaPlayerThread = new MediaPlayerThread(); - replay = true; + }); } - /** - * Load the VLC library dll using JNA. - * - * @return true, if the library was loaded correctly. - * false, otherwise. - */ - private boolean initVlc() { + private boolean initGst() { try { - Native.loadLibrary(RuntimeUtil.getLibVlcLibraryName(), LibVlc.class); - vlcInited = true; - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error initalizing vlc for audio/video viewing and extraction capabilities", e); - MessageNotifyUtil.Notify.error("Error initializing vlc for audio/video viewing and frame extraction capabilities. " - + " Video and audio viewing will be disabled. ", e.getMessage()); - vlcInited = false; + 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 vlcInited; + + return true; } /** @@ -205,7 +187,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt void setupVideo(final AbstractFile file, final Dimension dims) { infoLabel.setText(""); currentFile = file; - replay = false; 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."); @@ -214,7 +195,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt progressSlider.setEnabled(false); return; } - + String path = ""; try { path = file.getUniquePath(); @@ -225,29 +206,37 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt infoLabel.setToolTipText(path); pauseButton.setEnabled(true); progressSlider.setEnabled(true); - - // Create and Configure MediaPlayer - vlcVideoComponent = new EmbeddedMediaPlayerComponent(); - vlcMediaPlayer = vlcVideoComponent.getMediaPlayer(); - vlcMediaPlayer.setPlaySubItems(true); - vlcMediaPlayer.addMediaPlayerEventListener(new VlcMediaPlayerEventListener()); - - // Configure VideoPanel - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(vlcVideoComponent); - videoPanel.setVisible(true); - - // Create Extraction and Playback Threads - mediaPlayerThread = new MediaPlayerThread(); - ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); - em.execute(); + + 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); + } + } + } - /** - * Cleanup all threads and VLC components and reset UI. - */ void reset() { + // reset the progress label text on the event dispatch thread SwingUtilities.invokeLater(new Runnable() { @Override @@ -259,10 +248,27 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt if (!isInited()) { return; } - - if (mediaPlayerThread != null) { - mediaPlayerThread.cancel(); - mediaPlayerThread = null; + + 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 @@ -271,27 +277,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt videoProgressWorker = null; } - if (vlcMediaPlayer != null) { - if (vlcMediaPlayer.isPlaying()) { - vlcMediaPlayer.stop(); - } - vlcMediaPlayer.release(); - vlcMediaPlayer = null; - } - if (vlcVideoComponent != null) { - vlcVideoComponent.release(true); - vlcVideoComponent = null; - } - currentFile = null; - currentVideoFile = null; - } - - /** - * Start the media player. - */ - void playMedia() { - SwingUtilities.invokeLater(mediaPlayerThread); } private java.io.File getJFile(AbstractFile file) { @@ -321,59 +307,134 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt public List captureFrames(java.io.File file, int numFrames) throws Exception { List frames = new ArrayList<>(); - + + Object lock = new Object(); + FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock); + if (!isInited()) { return frames; } - - // Create Media Player with no video output - MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory(VLC_FRAME_CAPTURE_ARGS); - MediaPlayer mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer(); - boolean mediaPrepared = mediaPlayer.prepareMedia(file.getAbsolutePath()); - if (!mediaPrepared) { - logger.log(Level.WARNING, "Video file was not loaded properly."); - 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 - mediaPlayer.parseMedia(); - long myDurationMillis = mediaPlayer.getMediaMeta().getLength(); + 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 - INTER_FRAME_PERIOD_MS) / numFrames; + long frameInterval = myDurationMillis / numFrames; if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { numFramesToGet = 1; } - mediaPlayer.start(); - mediaPlayer.setPause(true); - - BufferedImage snapShot; // for each timeStamp, grap a frame for (int i = 0; i < numFramesToGet; ++i) { - logger.log(Level.INFO, "Grabbing a frame..."); - long timeStamp = i * frameInterval + INTER_FRAME_PERIOD_MS; + long timeStamp = i * frameInterval; - mediaPlayer.setTime(timeStamp); - mediaPlayer.setPause(true); - - snapShot = mediaPlayer.getSnapshot(); - - if (snapShot == null) { - continue; + 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."); } - frames.add(new VideoFrame(snapShot, timeStamp)); + 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)); } - // cleanup media player - mediaPlayer.release(); - mediaPlayerFactory.release(); 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. @@ -409,8 +470,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } }); - progressSlider.setSnapToTicks(true); - 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 @@ -459,14 +518,39 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt }// //GEN-END:initComponents private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed - if (replay) { - // File has completed playing. Play button now replays media. - replay = false; - playMedia(); - } else if (vlcMediaPlayer.isPlaying()) { - this.pause(); - } else { - this.unPause(); + 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 @@ -478,29 +562,53 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables - /** - * Thread that updates the video progress bar and current time with information - * queried from the MediaPlayer. - */ private class VideoProgressWorker extends SwingWorker { private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; private long millisElapsed = 0; - private float currentPosition = 0.0f; + private final long INTER_FRAME_PERIOD_MS = 20; + private final long END_TIME_MARGIN_MS = 50; + private boolean hadError = false; - private boolean isMediaPlayerReady() { - return vlcMediaPlayer != null; + private boolean isPlayBinReady() { + synchronized (playbinLock) { + return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL); + } } - // TODO: Could be moved to finished() 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 { @@ -508,10 +616,14 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt progressSlider.setEnabled(true); int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; - while (isMediaPlayerReady() && !isCancelled()) { - millisElapsed = vlcMediaPlayer.getTime(); - currentPosition = vlcMediaPlayer.getPosition(); - + 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; @@ -526,7 +638,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt progressLabel.setText(durationStr); autoTracking = true; - progressSlider.setValue((int) (currentPosition * POS_FACTOR)); + progressSlider.setValue((int) millisElapsed); autoTracking = false; try { @@ -545,16 +657,15 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } } //end class progress worker - /** - * Thread that extracts Media from a Sleuthkit file representation to a - * Java file representation that the Media Player can take as input. - */ + /* Thread that extracts and plays a file */ private class ExtractMedia extends SwingWorker { 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) { @@ -583,7 +694,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } catch (IOException ex) { logger.log(Level.WARNING, "Error buffering file", ex); } - logger.log(Level.INFO, "Done buffering: " + jFile.getName()); success = true; return null; } @@ -602,35 +712,34 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt } finally { progress.finish(); if (!this.isCancelled()) { - logger.log(Level.INFO, "ExtractMedia in done: " + jFile.getName()); - currentVideoFile = jFile; playMedia(); } } } - } - - /** - * Thread that is responsible for running the Media Player. - */ - private class MediaPlayerThread implements Runnable, Cancellable { - private volatile boolean cancelled = false; - - /* Prepare and run the current media. */ - @Override - public void run() { - if (currentVideoFile == null || !currentVideoFile.exists()) { + void playMedia() { + if (jFile == null || !jFile.exists()) { progressLabel.setText("Error buffering file"); return; - } - boolean mediaPrepared = vlcMediaPlayer.prepareMedia(currentVideoFile.getAbsolutePath()); - if (mediaPrepared) { - vlcMediaPlayer.parseMedia(); - durationMillis = vlcMediaPlayer.getMediaMeta().getLength(); - } else { - progressLabel.setText(MEDIA_PLAYER_ERROR_STRING); } + 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; @@ -640,45 +749,23 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt durationSeconds -= totalMinutes * 60; totalSeconds = (int) durationSeconds; - progressSlider.setMaximum(POS_FACTOR); - progressSlider.setMinimum(0); - if (!isCancelled()) { - vlcMediaPlayer.start(); - pauseButton.setText("||"); - videoProgressWorker = new VideoProgressWorker(); - videoProgressWorker.execute(); - } else { - logger.log(Level.INFO, "Media Playing cancelled."); - } - } + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressSlider.setMaximum((int) durationMillis); + progressSlider.setMinimum(0); - @Override - public boolean cancel() { - cancelled = true; - return cancelled; - } - - private boolean isCancelled() { - return cancelled; - } - - } - - /** - * An Object that listens and handles events thrown by the VLC Media Player. - */ - private class VlcMediaPlayerEventListener extends MediaPlayerEventAdapter { - - @Override - public void finished(MediaPlayer mediaPlayer) { - logger.log(Level.INFO, "Media Player finished playing the media"); - finished(); - } - - @Override - public void error(MediaPlayer mediaPlayer) { - logger.log(Level.WARNING, "A VLC error occured. Resetting the video panel."); - reset(); + 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(); + } + }); } } } From 288cbf1d1768787073bd91315357d16e9d02df2b Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 13 Aug 2013 10:48:29 -0400 Subject: [PATCH 07/21] Changed MediaViewPlayerPanel to be an abstract class and added gstreamer and javafx concrete subclasses. --- .../DataContentViewerMedia.java | 2 +- .../autopsy/corecomponents/FXVideoPanel.java | 648 +++++++++++++ .../autopsy/corecomponents/GstVideoPanel.java | 766 +++++++++++++++ .../corecomponents/MediaViewVideoPanel.form | 113 --- .../corecomponents/MediaViewVideoPanel.java | 888 +++--------------- 5 files changed, 1532 insertions(+), 885 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java delete mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index 17d9cd2f63..13abbad67b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -65,7 +65,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo initComponents(); - videoPanel = new MediaViewVideoPanel(); + videoPanel = MediaViewVideoPanel.createVideoPanel(); imagePanel = new MediaViewImagePanel(); videoPanelInited = videoPanel.isInited(); imagePanelInited = imagePanel.isInited(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java new file mode 100644 index 0000000000..e1cabd55f5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -0,0 +1,648 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +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.Pane; +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.Exceptions; +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") + // + 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) + ); + }// + + // 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 { + + 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); + + 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() { + @Override + public void changed(ObservableValue observable, Duration oldValue, Duration newValue) { + updateSlider(newValue); + updateTime(newValue); + } + }); + } + + private void setProgressActionListeners() { + pauseButton.setOnAction(new EventHandler() { + @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 captureFrames(java.io.File file, int numFrames) throws Exception { +// +// try { +// List 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; +// } + throw new UnsupportedOperationException("Frame Capture not implemented with JavaFx"); + } + +// 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 getFrames(int numFrames) { +// logger.log(Level.INFO, "in get frames"); +// List frames = new ArrayList(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; +// } +// } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java new file mode 100644 index 0000000000..2fdb28c45a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java @@ -0,0 +1,766 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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 badVideoFiles = Collections.synchronizedSet(new HashSet()); + + /** + * 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 captureFrames(java.io.File file, int numFrames) throws Exception { + + List 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") + // + 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()) + ); + }// + + private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) { + 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(); + } + } + } + // 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 { + + 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 { + + 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(); + } + }); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form deleted file mode 100644 index ceab29b1e8..0000000000 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form +++ /dev/null @@ -1,113 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index da1ca268d5..15886f7293 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -1,771 +1,117 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 Basis Technology Corp. - * Contact: carrier sleuthkit 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; - -/** - * Video viewer part of the Media View layered pane - */ -@ServiceProviders(value = { - @ServiceProvider(service = FrameCapture.class) -}) -public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { - - private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); - private boolean gstInited; - private static final long MIN_FRAME_INTERVAL_MILLIS = 500; - private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000; - private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file."; - //playback - private long durationMillis = 0; - private VideoProgressWorker videoProgressWorker; - private int totalHours, totalMinutes, totalSeconds; - private volatile PlayBin2 gstPlaybin2; - private VideoComponent gstVideoComponent; - private boolean autoTracking = false; // true if the slider is moving automatically - private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player - private AbstractFile currentFile; - private Set badVideoFiles = Collections.synchronizedSet(new HashSet()); - - /** - * Creates new form MediaViewVideoPanel - */ - public MediaViewVideoPanel() { - initComponents(); - customizeComponents(); - } - - public JButton getPauseButton() { - return pauseButton; - } - - public JLabel getProgressLabel() { - return progressLabel; - } - - public JSlider getProgressSlider() { - return progressSlider; - } - - public JPanel getVideoPanel() { - return videoPanel; - } - - public VideoComponent getVideoComponent() { - return gstVideoComponent; - } - - public boolean isInited() { - return gstInited; - } - - private void customizeComponents() { - if (!initGst()) { - return; - } - - 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; - } - - /** - * Initialize all the necessary vars to play a video/audio file. - * - * @param file video file to play - * @param dims dimension of the parent window - */ - void setupVideo(final AbstractFile file, final Dimension dims) { - infoLabel.setText(""); - currentFile = file; - final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC); - if (deleted) { - infoLabel.setText("Playback of deleted videos is not supported, use an external player."); - videoPanel.removeAll(); - pauseButton.setEnabled(false); - progressSlider.setEnabled(false); - return; - } - - String path = ""; - try { - path = file.getUniquePath(); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Cannot get unique path of video file"); - } - infoLabel.setText(path); - infoLabel.setToolTipText(path); - pauseButton.setEnabled(true); - progressSlider.setEnabled(true); - - java.io.File ioFile = getJFile(file); - - gstVideoComponent = new VideoComponent(); - synchronized (playbinLock) { - if (gstPlaybin2 != null) { - gstPlaybin2.dispose(); - } - gstPlaybin2 = new PlayBin2("VideoPlayer"); - gstPlaybin2.setVideoSink(gstVideoComponent.getElement()); - - videoPanel.removeAll(); - - - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(gstVideoComponent); - - videoPanel.setVisible(true); - - gstPlaybin2.setInputFile(ioFile); - - if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } - } - - } - - void reset() { - - // reset the progress label text on the event dispatch thread - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressLabel.setText(""); - } - }); - - if (!isInited()) { - return; - } - - synchronized (playbinLock) { - if (gstPlaybin2 != null) { - if (gstPlaybin2.isPlaying()) { - if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - } - if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - if (gstPlaybin2.getState().equals(State.NULL)) { - gstPlaybin2.dispose(); - } - gstPlaybin2 = null; - } - gstVideoComponent = null; - } - - // get rid of any existing videoProgressWorker thread - if (videoProgressWorker != null) { - videoProgressWorker.cancel(true); - videoProgressWorker = null; - } - - currentFile = null; - } - - private java.io.File getJFile(AbstractFile file) { - // Get the temp folder path of the case - String tempPath = Case.getCurrentCase().getTempDirectory(); - String name = file.getName(); - int extStart = name.lastIndexOf("."); - String ext = ""; - if (extStart != -1) { - ext = name.substring(extStart, name.length()).toLowerCase(); - } - tempPath = tempPath + java.io.File.separator + file.getId() + ext; - - java.io.File tempFile = new java.io.File(tempPath); - return tempFile; - } - - /** - * @param file a video file from which to capture frames - * @param numFrames the number of frames to capture. These frames will be - * captured at successive intervals given by durationOfVideo/numFrames. If - * this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one - * frame will be captured and returned. - * @return a List of VideoFrames representing the captured frames. - */ - @Override - public List captureFrames(java.io.File file, int numFrames) throws Exception { - - List 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") - // //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()) - ); - }// //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 { - - 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 { - - 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(); - } - }); - } - } -} +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JPanel; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Video viewer part of the Media View layered pane. + */ +public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture { + + private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); + + // 64 bit architectures + private static final String[] ARCH64 = new String[]{"amd64", "x86_64"}; + + // 32 bit architectures + private static final String[] ARCH32 = new String[]{"x86"}; + + // A Gstreamer implementation of MediaViewVideoPanel + private static GstVideoPanel gstVideoPanel = null; + + // A JavaFX implmentation of MediaViewVideoPanel + private static FXVideoPanel fxVideoPanel = null; + + /** + * Factory Method to create a MediaViewVideoPanel. + * + * Implementation is dependent on the architecture of the JVM. + * + * @return a MediaViewVideoPanel instance. + */ + public static MediaViewVideoPanel createVideoPanel() { + if (is64BitJVM()) { + logger.log(Level.INFO, "64 bit JVM detected. Creating JavaFX Video Player."); + return getFXImpl(); + } else { + logger.log(Level.INFO, "32 bit JVM detected. Creating GStreamer Video Player."); + return getGstImpl(); + } + } + + /** + * Is the JVM architecture 64 bit? + * + * @return + */ + private static boolean is64BitJVM() { + String arch = System.getProperty("os.arch"); + return Arrays.asList(ARCH64).contains(arch); + } + + /** + * Get a GStreamer video player implementation. + * + * @return a GstVideoPanel + */ + private static MediaViewVideoPanel getGstImpl() { + if (gstVideoPanel == null) { + gstVideoPanel = new GstVideoPanel(); + } + return gstVideoPanel; + } + + /** + * 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. + * + * @param file video file to play + * @param dims dimension of the parent window + */ + abstract void setupVideo(final AbstractFile file, final Dimension dims); +} From b6a5c0321c80bb7ddd3c056abfcc4881a3734009 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 13 Aug 2013 12:42:33 -0400 Subject: [PATCH 08/21] Added small style change to javafx media player. --- Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java index e1cabd55f5..5ac8f0d8c5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -378,7 +378,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { mediaTools.getChildren().add(progressLabel); controlPanel.getChildren().add(mediaTools); - + controlPanel.setStyle("-fx-background-color: white"); infoLabel = new Label(""); controlPanel.getChildren().add(infoLabel); setBottom(controlPanel); From 15dc36740ca35c60815f6d660c960a1b7a96957d Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 13 Aug 2013 12:52:36 -0400 Subject: [PATCH 09/21] Small fix to javafx frame capture. --- Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java index 5ac8f0d8c5..e3faf121c6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -554,7 +554,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { // e.printStackTrace(); // return null; // } - throw new UnsupportedOperationException("Frame Capture not implemented with JavaFx"); + return null; } // private class FrameCapturer { From 8da7e7bc3ea2391b8c620dde252c97dfe1e323e3 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 13 Aug 2013 12:58:13 -0400 Subject: [PATCH 10/21] Updated project.xml in Core to remove vlc dependency --- Core/nbproject/project.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index dc5a236e24..312c321dd0 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -213,14 +213,6 @@ ext/Tsk_DataModel.jar release/modules/ext/Tsk_DataModel.jar - - ext/vlcj-2.4.1.jar - release/modules/ext/vlcj-2.4.1.jar - - - ext/jna-3.5.2.jar - release/modules/ext/jna-3.5.2.jar - From 0298450238726ea86aa422b6f358dc1083d359bb Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Wed, 14 Aug 2013 15:22:07 -0400 Subject: [PATCH 11/21] Removed library loading functions from the core installer and updated the build script, which doesn't need to get the tsk libs anymore. --- .../org/sleuthkit/autopsy/core/Installer.java | 381 ++++++------- build-windows.xml | 324 +++++------ build.xml | 538 +++++++++--------- 3 files changed, 593 insertions(+), 650 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 218c00c3aa..fcac88d7c3 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -1,216 +1,165 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 Basis Technology Corp. - * Contact: carrier sleuthkit 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.core; - -import com.sun.javafx.application.PlatformImpl; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javafx.application.Platform; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.openide.modules.ModuleInstall; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; - -/** - * Wrapper over Installers in packages in Core module This is the main - * registered installer in the MANIFEST.MF - */ -public class Installer extends ModuleInstall { - - private List packageInstallers; - private static final Logger logger = Logger.getLogger(Installer.class.getName()); - private volatile boolean javaFxInit = true; - - static { - loadDynLibraries(); - } - - private static void loadDynLibraries() { - if (PlatformUtil.isWindowsOS()) { - try { - //on windows force loading ms crt dependencies first - //in case linker can't find them on some systems - //Note: if shipping with a different CRT version, this will only print a warning - //and try to use linker mechanism to find the correct versions of libs. - //We should update this if we officially switch to a new version of CRT/compiler - System.loadLibrary("msvcr100"); - System.loadLibrary("msvcp100"); - logger.log(Level.INFO, "MS CRT libraries loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading ms crt libraries, ", e); - } - } - - try { - System.loadLibrary("zlib"); - logger.log(Level.INFO, "ZLIB library loaded loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading ZLIB library, ", e); - } - - try { - System.loadLibrary("libewf"); - logger.log(Level.INFO, "EWF library loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading EWF library, ", e); - } - - /* We should rename the Windows dll, to remove the lib prefix. - */ - try { - String tskLibName = null; - if (PlatformUtil.isWindowsOS()) { - tskLibName = "libtsk_jni"; - } else { - tskLibName = "tsk_jni"; - } - System.loadLibrary(tskLibName); - logger.log(Level.INFO, "TSK_JNI library loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading tsk_jni library", e); - } - } - - public Installer() { - javaFxInit = true; - packageInstallers = new ArrayList(); - - packageInstallers.add(org.sleuthkit.autopsy.coreutils.Installer.getDefault()); - packageInstallers.add(org.sleuthkit.autopsy.corecomponents.Installer.getDefault()); - packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); - packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); - - } - - /** - * Check if JavaFx initialized - * @return false if java fx not initialized (classes coult not load), true if initialized - */ - public boolean isJavaFxInited() { - return this.javaFxInit; - } - - private void initJavaFx() { - //initialize java fx if exists - try { - Platform.setImplicitExit(false); - PlatformImpl.startup(new Runnable() { - @Override - public void run() { - logger.log(Level.INFO, "Initializing JavaFX for image viewing"); - } - }); - } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { - //in case javafx not present - javaFxInit = false; - final String msg = "Error initializing JavaFX. "; - final String details = " Some features will not be available. " - + " Check that you have the right JRE installed (Oracle JRE > 1.7.10). "; - logger.log(Level.SEVERE, msg - + details, e); - - WindowManager.getDefault().invokeWhenUIReady(new Runnable() { - @Override - public void run() { - MessageNotifyUtil.Notify.error(msg, details); - } - }); - - - } - - } - - @Override - public void restored() { - super.restored(); - - logger.log(Level.INFO, "restored()"); - - initJavaFx(); - - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " restored()"); - try { - mi.restored(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - - } - - @Override - public void validate() throws IllegalStateException { - super.validate(); - - logger.log(Level.INFO, "validate()"); - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " validate()"); - try { - mi.validate(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - } - - @Override - public void uninstalled() { - super.uninstalled(); - - logger.log(Level.INFO, "uninstalled()"); - - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " uninstalled()"); - try { - mi.uninstalled(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - - - - } - - @Override - public void close() { - super.close(); - - logger.log(Level.INFO, "close()"); - - //exit JavaFx plat - if (javaFxInit) { - Platform.exit(); - } - - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " close()"); - try { - mi.close(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - } -} +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier sleuthkit 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.core; + +import com.sun.javafx.application.PlatformImpl; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javafx.application.Platform; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.modules.ModuleInstall; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +/** + * Wrapper over Installers in packages in Core module This is the main + * registered installer in the MANIFEST.MF + */ +public class Installer extends ModuleInstall { + + private List packageInstallers; + private static final Logger logger = Logger.getLogger(Installer.class.getName()); + private volatile boolean javaFxInit = true; + + public Installer() { + javaFxInit = true; + packageInstallers = new ArrayList(); + + packageInstallers.add(org.sleuthkit.autopsy.coreutils.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.corecomponents.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); + + } + + /** + * Check if JavaFx initialized + * @return false if java fx not initialized (classes coult not load), true if initialized + */ + public boolean isJavaFxInited() { + return this.javaFxInit; + } + + private void initJavaFx() { + //initialize java fx if exists + try { + Platform.setImplicitExit(false); + PlatformImpl.startup(new Runnable() { + @Override + public void run() { + logger.log(Level.INFO, "Initializing JavaFX for image viewing"); + } + }); + } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { + //in case javafx not present + javaFxInit = false; + final String msg = "Error initializing JavaFX. "; + final String details = " Some features will not be available. " + + " Check that you have the right JRE installed (Oracle JRE > 1.7.10). "; + logger.log(Level.SEVERE, msg + + details, e); + + WindowManager.getDefault().invokeWhenUIReady(new Runnable() { + @Override + public void run() { + MessageNotifyUtil.Notify.error(msg, details); + } + }); + + + } + + } + + @Override + public void restored() { + super.restored(); + + logger.log(Level.INFO, "restored()"); + + initJavaFx(); + + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " restored()"); + try { + mi.restored(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + + } + + @Override + public void validate() throws IllegalStateException { + super.validate(); + + logger.log(Level.INFO, "validate()"); + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " validate()"); + try { + mi.validate(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + } + + @Override + public void uninstalled() { + super.uninstalled(); + + logger.log(Level.INFO, "uninstalled()"); + + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " uninstalled()"); + try { + mi.uninstalled(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + + + + } + + @Override + public void close() { + super.close(); + + logger.log(Level.INFO, "close()"); + + //exit JavaFx plat + if (javaFxInit) { + Platform.exit(); + } + + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " close()"); + try { + mi.close(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + } +} diff --git a/build-windows.xml b/build-windows.xml index bbc4e0e098..34a8c0b212 100644 --- a/build-windows.xml +++ b/build-windows.xml @@ -1,168 +1,156 @@ - - - - Release - - - - - - - - - - - - - - - - - - - - 10.0.30319.1 - - - - - - - - - - ${thirdparty.dir}/crt/x86-32/${CRT.version}/crt.zip - - - - - - - - - - - - - - C:\Program Files (x86)\Caphyon\Advanced Installer 10.2\bin\x86\AdvancedInstaller.com - - - - - - - - - - - - - - - - - - - Product Code: ${guid1} - - Product Version: ${app.version} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Release + + + + + + + + 10.0.30319.1 + + + + + + + + + + ${thirdparty.dir}/crt/x86-32/${CRT.version}/crt.zip + + + + + + + + + + + + + + C:\Program Files (x86)\Caphyon\Advanced Installer 10.2\bin\x86\AdvancedInstaller.com + + + + + + + + + + + + + + + + + + + Product Code: ${guid1} + + Product Version: ${app.version} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.xml b/build.xml index cbbefbee0b..300bbfcb95 100644 --- a/build.xml +++ b/build.xml @@ -1,266 +1,272 @@ - - - - - - Builds the module suite Autopsy3. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TSK_HOME: ${env.TSK_HOME} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Suite in ${basedir} with clusters ${cluster.path.final}, build cluster ${cluster}, and sorted modules ${modules.sorted} - - - - - - - - - - - - - - - - - - - - - - - - - - - ${app.name} branding - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + Builds the module suite Autopsy3. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TSK_HOME: ${env.TSK_HOME} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Suite in ${basedir} with clusters ${cluster.path.final}, build cluster ${cluster}, and sorted modules ${modules.sorted} + + + + + + + + + + + + + + + + + + + + + + + + + + + ${app.name} branding + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f0dd4f56872560b54ed04642a3ca106484d3969c Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Wed, 14 Aug 2013 15:57:53 -0400 Subject: [PATCH 12/21] Updated output formatting and added return value enumeration. --- update_versions.py | 73 +++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/update_versions.py b/update_versions.py index e33d242946..ce9b857117 100644 --- a/update_versions.py +++ b/update_versions.py @@ -23,9 +23,9 @@ # # http://wiki.sleuthkit.org/index.php?title=Autopsy_3_Module_Versions # -# The basic idea is that this script uses javadoc/jdiff to -# compare the current state of the source code to the last -# tag and identifies if APIs were removed, added, etc. +# The basic idea is that this script uses javadoc/jdiff to +# compare the current state of the source code to the last +# tag and identifies if APIs were removed, added, etc. # # When run from the Autopsy build script, this script will: # - Clone Autopsy and checkout to the previous release tag @@ -61,6 +61,12 @@ from shutil import move from tempfile import mkstemp from xml.dom.minidom import parse, parseString +# Jdiff return codes. Described in more detail further on +NO_CHANGES = 100 +COMPATIBLE = 101 +NON_COMPATIBLE = 102 +ERROR = 1 + # An Autopsy module object class Module: # Initialize it with a name, return code, and version numbers @@ -218,11 +224,11 @@ def compare_xml(module, apiname_tag, apiname_cur): log.close() code = jdiff.returncode print("Compared XML for " + module.name) - if code == 100: + if code == NO_CHANGES: print(" No API changes") - elif code == 101: + elif code == COMPATIBLE: print(" API Changes are backwards compatible") - elif code == 102: + elif code == NON_COMPATIBLE: print(" API Changes are not backwards compatible") else: print(" *Error in XML, most likely an empty module") @@ -564,18 +570,18 @@ def update_versions(modules, source): if manifest == None or project == None: print(" Error finding manifeset and project properties files") return - if module.ret == 101: + if module.ret == COMPATIBLE: versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]] set_specification(project, manifest, versions[0]) set_implementation(manifest, versions[1]) module.set_versions(versions) - elif module.ret == 102: + elif module.ret == NON_COMPATIBLE: versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1] set_specification(project, manifest, versions[0]) set_implementation(manifest, versions[1]) set_release(manifest, versions[2]) module.set_versions(versions) - elif module.ret == 100: + elif module.ret == NO_CHANGES: versions = [versions[0], versions[1] + 1, versions[2]] set_implementation(manifest, versions[1]) module.set_versions(versions) @@ -624,48 +630,40 @@ def print_version_updates(modules): f = open("gen_version.txt", "a") for module in modules: versions = module.versions - if module.ret == 101: + if module.ret == COMPATIBLE: output = (module.name + ":\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Updated Specification version:\t" + str(versions[0].increment()) + "\n") - output += ("\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") + output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].increment()) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n") + output += ("\tRelease:\tNo Change.\n") output += ("\n") print(output) sys.stdout.flush() f.write(output) - elif module.ret == 102: + elif module.ret == NON_COMPATIBLE: output = (module.name + ":\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Updated Specification version:\t" + str(versions[0].overflow()) + "\n") - output += ("\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") - output += ("\n") - output += (" Current Release version:\t\t" + str(versions[2]) + "\n") - output += (" Updated Release version:\t\t" + str(versions[2] + 1) + "\n") + output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].overflow()) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n") + output += ("\tRelease:\t" + str(versions[2]) + "\t->\t" + str(versions[2] + 1) + "\n") output += ("\n") print(output) sys.stdout.flush() f.write(output) - elif module.ret == 1: + elif module.ret == ERROR: output = (module.name + ":\n") - output += (" *Unable to detect necessary changes\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Current Release version:\t\t" + str(versions[2]) + "\n") + output += ("\t*Unable to detect necessary changes\n") + output += ("\tSpecification:\t" + str(versions[0]) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\n") + output += ("\tRelease:\t\t" + str(versions[2]) + "\n") output += ("\n") print(output) f.write(output) sys.stdout.flush() - elif module.ret == 100: + elif module.ret == NO_CHANGES: output = (module.name + ":\n") if versions[1] is None: - output += (" No Implementation version.\n") + output += ("\tImplementation: None\n") else: - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n") output += ("\n") print(output) sys.stdout.flush() @@ -673,16 +671,13 @@ def print_version_updates(modules): elif module.ret is None: output = ("Added " + module.name + ":\n") if module.spec() != "1.0" and module.spec() != "0.0": - output += (" Current Specification version:\t" + str(module.spec()) + "\n") - output += (" Updated Specification version:\t1.0\n") + output += ("\tSpecification:\t" + str(module.spec()) + "\t->\t" + "1.0\n") output += ("\n") if module.impl() != 1: - output += (" Current Implementation version:\t" + str(module.impl()) + "\n") - output += (" Updated Implementation version:\t1\n") + output += ("\tImplementation:\t" + str(module.impl()) + "\t->\t" + "1\n") output += ("\n") if module.release() != 1 and module.release() != 0: - output += (" Current Release version:\t\t" + str(module.release()) + "\n") - output += (" Updated Release version:\t\t1\n") + output += ("Release:\t\t" + str(module.release()) + "\t->\t" + "1\n") output += ("\n") print(output) sys.stdout.flush() From cf98b8b1e8a3d5dac6b4f1c93f109e613926e0b4 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Fri, 16 Aug 2013 12:58:42 -0400 Subject: [PATCH 13/21] Updated build windows script, which doesn't deal with native libraries anymore. --- build-windows.xml | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/build-windows.xml b/build-windows.xml index 34a8c0b212..e73e97fcd6 100644 --- a/build-windows.xml +++ b/build-windows.xml @@ -3,44 +3,6 @@ Release - - - - - - - 10.0.30319.1 - - - - - - - - - - ${thirdparty.dir}/crt/x86-32/${CRT.version}/crt.zip - - - - - - - - - - - - C:\Program Files (x86)\Caphyon\Advanced Installer 10.2\bin\x86\AdvancedInstaller.com Date: Fri, 16 Aug 2013 13:38:36 -0400 Subject: [PATCH 14/21] Removed outdated reference to ant target. --- build.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/build.xml b/build.xml index 300bbfcb95..51bb4f7eef 100644 --- a/build.xml +++ b/build.xml @@ -84,7 +84,6 @@ - From 2d0218d8a4f582345126c22f14a62c02f20cadc8 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Thu, 22 Aug 2013 15:19:50 -0400 Subject: [PATCH 15/21] Refactored advanced installer script --- build-windows.xml | 137 +++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/build-windows.xml b/build-windows.xml index 7f40f14b63..d44420d0e8 100644 --- a/build-windows.xml +++ b/build-windows.xml @@ -3,22 +3,51 @@ Release - - C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com + + + + + + + + + + + + + + + + + + + + - - + + - - - - + + + + + + + + + + - - Product Code: ${guid1} Product Version: ${app.version} @@ -42,55 +69,36 @@ - - - + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + @@ -98,7 +106,10 @@ - + + + + @@ -112,14 +123,6 @@ - - - - - - - - - + From 4ffdc5bd72c6bfd9f1ce955314827be976a9b767 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Wed, 4 Sep 2013 11:25:04 -0400 Subject: [PATCH 16/21] Added more detailed messages when data is not available for keyword searching --- .../AbstractKeywordSearchPerformer.java | 13 +++++++++++-- .../autopsy/keywordsearch/KeywordSearchPanel.java | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java index 8fc05094df..1cfdae3774 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java @@ -83,13 +83,22 @@ abstract class AbstractKeywordSearchPerformer extends javax.swing.JPanel impleme @Override public void search() { + boolean isRunning = IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault()); + if (filesIndexed == 0) { - KeywordSearchUtil.displayDialog("Keyword Search Error", "No files are indexed, please index an image before searching", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + if (isRunning) { + KeywordSearchUtil.displayDialog("Keyword Search Error", "No files are in index yet.
" + + "Try again later. Index is updated every " + KeywordSearchSettings.getUpdateFrequency().getTime() + " minutes.", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + } + else { + KeywordSearchUtil.displayDialog("Keyword Search Error", "No files were indexed.
" + + "Re-ingest the image with the Keyword Search Module enabled. ", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + } return; } //check if keyword search module ingest is running (indexing, etc) - if (IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault())) { + if (isRunning) { if (KeywordSearchUtil.displayConfirmDialog("Keyword Search Ingest in Progress", "Keyword Search Ingest is currently running.
" + "Not all files have been indexed and this search might yield incomplete results.
" diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java index 42576cba74..1f77cdcc47 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java @@ -43,7 +43,7 @@ import org.apache.solr.client.solrj.SolrServerException; import org.sleuthkit.autopsy.casemodule.Case; /** - * Keyword search toolbar which allows to search for single terms or phrases + * Keyword search toolbar (in upper right, by default) which allows to search for single terms or phrases * * The toolbar uses a different font from the rest of the application, Monospaced 14, * due to the necessity to find a font that displays both Arabic and Asian fonts at an acceptable size. From 43025d2bc6e74ac9dd0467f1809bb051a929e050 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 4 Sep 2013 12:53:03 -0400 Subject: [PATCH 17/21] Eliminated setting the display 2x when a selected artifact is found; removed always false check for ArtifactStringContent object in Lookup of selected node --- .../DataContentViewerArtifact.java | 58 ++++++++----------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 5ecab8489f..95c6750cab 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,6 @@ import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.JTextPane; -import javax.swing.text.Document; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; import org.openide.nodes.Node; @@ -204,12 +203,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed currentPage = currentPage+1; - setDataView(artifacts, currentPage); + setDataView(currentPage); }//GEN-LAST:event_nextPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed currentPage = currentPage-1; - setDataView(artifacts, currentPage); + setDataView(currentPage); }//GEN-LAST:event_prevPageButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables @@ -239,20 +238,30 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat Content content = lookup.lookup(Content.class); if (content == null) { resetComponent(); - return; + return; } - + try { - this.setDataView(content.getAllArtifacts(), 1); - } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't get artifacts: ", ex); + artifacts = content.getAllArtifacts(); + } + catch (TskException ex) { + logger.log(Level.WARNING, "Couldn't get artifacts", ex); + resetComponent(); + return; } - + // focus on a specific artifact if it is in the node + int index = 0; BlackboardArtifact artifact = lookup.lookup(BlackboardArtifact.class); if (artifact != null) { - this.setSelectedArtifact(artifact); - } + index = artifacts.indexOf(artifact); + if (index == -1) { + index = 0; + } + } + + // A little bit of cleverness here - add one since setDataView() is also passed page numbers. + setDataView(index + 1); } @Override @@ -279,7 +288,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat public void resetComponent() { // clear / reset the fields currentPage = 1; - this.artifacts = new ArrayList(); + this.artifacts = new ArrayList<>(); currentPageLabel.setText(""); totalPageLabel.setText(""); outputViewPane.setText(""); @@ -311,12 +320,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat return false; } - ArtifactStringContent artifact = node.getLookup().lookup(ArtifactStringContent.class); - Content content = node.getLookup().lookup(Content.class); - - if(artifact != null) { - return true; - } + Content content = node.getLookup().lookup(Content.class); if(content != null) { try { long size = content.getAllArtifactsCount(); @@ -380,7 +384,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat * @param artifacts List of artifacts that could be displayed * @param offset Index into the list for the artifact to display */ - private void setDataView(List artifacts, int offset) { + private void setDataView(int offset) { // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); @@ -388,7 +392,6 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat resetComponent(); return; } - this.artifacts = artifacts; StringContent artifactString = new ArtifactStringContent(artifacts.get(offset-1)); String text = artifactString.getString(); @@ -406,16 +409,5 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat setComponentsVisibility(true); outputViewPane.moveCaretPosition(0); this.setCursor(null); - } - - /** - * Set the displayed artifact to the specified one. - * @param artifact Artifact to display - */ - private void setSelectedArtifact(BlackboardArtifact artifact) { - if(artifacts.contains(artifact)) { - int index = artifacts.indexOf(artifact); - setDataView(artifacts, index+1); - } - } + } } From 27d1d10200215b89f7a08f2d17bf2d1e9602b715 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 4 Sep 2013 13:18:49 -0400 Subject: [PATCH 18/21] Added worker thread to do display for DataContentViewerArtifact --- .../DataContentViewerArtifact.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 95c6750cab..bba2eb9100 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -34,6 +34,7 @@ import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.JTextPane; +import javax.swing.SwingWorker; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; import org.openide.nodes.Node; @@ -55,7 +56,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private static int currentPage = 1; private List artifacts; private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName()); - + private final static String WAIT_TEXT = "Preparing display..."; + /** Creates new form DataContentViewerArtifact */ public DataContentViewerArtifact() { initComponents(); @@ -203,12 +205,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed currentPage = currentPage+1; - setDataView(currentPage); + new DisplayTask(currentPage).execute(); }//GEN-LAST:event_nextPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed currentPage = currentPage-1; - setDataView(currentPage); + new DisplayTask(currentPage).execute(); }//GEN-LAST:event_prevPageButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables @@ -261,7 +263,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } // A little bit of cleverness here - add one since setDataView() is also passed page numbers. - setDataView(index + 1); + new DisplayTask(index + 1).execute(); } @Override @@ -385,9 +387,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat * @param offset Index into the list for the artifact to display */ private void setDataView(int offset) { - // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - + outputViewPane.setText(WAIT_TEXT); + if(artifacts.isEmpty()){ resetComponent(); return; @@ -410,4 +412,18 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat outputViewPane.moveCaretPosition(0); this.setCursor(null); } + + private class DisplayTask extends SwingWorker { + final int pageIndex; + + DisplayTask(final int pageIndex) { + this.pageIndex = pageIndex; + } + + @Override + public Integer doInBackground() { + setDataView(pageIndex); + return 0; + } + } } From 19ac7e5757918a03ab5f244064a365a8c7b6a0bb Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Wed, 4 Sep 2013 15:11:41 -0400 Subject: [PATCH 19/21] updated news and added comments --- Core/nbproject/project.xml | 436 ++--- .../org/sleuthkit/autopsy/core/Installer.java | 330 ++-- .../DataContentViewerMedia.form | 1 + .../DataContentViewerMedia.java | 6 +- .../autopsy/corecomponents/FXVideoPanel.java | 1290 +++++++------- .../autopsy/corecomponents/GstVideoPanel.java | 1532 ++++++++-------- .../corecomponents/MediaViewVideoPanel.java | 235 +-- NEWS.txt | 1578 +++++++++-------- build.xml | 4 +- 9 files changed, 2711 insertions(+), 2701 deletions(-) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 312c321dd0..9b49c8e266 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -1,218 +1,218 @@ - - - org.netbeans.modules.apisupport.project - - - org.sleuthkit.autopsy.core - - - - org.netbeans.api.progress - - - - 1 - 1.28.1 - - - - org.netbeans.core - - - - 2 - - - - - org.netbeans.core.startup - - - - 1 - - - - - org.netbeans.modules.javahelp - - - - 1 - 2.27.1 - - - - org.netbeans.modules.options.api - - - - 1 - 1.26.1 - - - - org.netbeans.modules.settings - - - - 1 - 1.35.1 - - - - org.netbeans.spi.quicksearch - - - - 1.14.1 - - - - org.netbeans.swing.outline - - - - 1.20.1 - - - - org.netbeans.swing.plaf - - - - 1.25.1 - - - - org.netbeans.swing.tabcontrol - - - - 1.36.1 - - - - org.openide.actions - - - - 6.26.1 - - - - org.openide.awt - - - - 7.46.1 - - - - org.openide.dialogs - - - - 7.25.1 - - - - org.openide.explorer - - - - 6.45.1 - - - - org.openide.filesystems - - - - 7.62.1 - - - - org.openide.modules - - - - 7.32.1 - - - - org.openide.nodes - - - - 7.28.1 - - - - org.openide.text - - - - 6.49.1 - - - - org.openide.util - - - - 8.25.1 - - - - org.openide.util.lookup - - - - 8.15.1 - - - - org.openide.windows - - - - 6.55.1 - - - - org.sleuthkit.autopsy.corelibs - - - - 3 - 1.0 - - - - - org.sleuthkit.autopsy.casemodule - org.sleuthkit.autopsy.casemodule.services - org.sleuthkit.autopsy.core - org.sleuthkit.autopsy.corecomponentinterfaces - org.sleuthkit.autopsy.corecomponents - org.sleuthkit.autopsy.coreutils - org.sleuthkit.autopsy.datamodel - org.sleuthkit.autopsy.directorytree - org.sleuthkit.autopsy.filesearch - org.sleuthkit.autopsy.ingest - org.sleuthkit.autopsy.menuactions - org.sleuthkit.autopsy.report - org.sleuthkit.datamodel - - - ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar - release/modules/ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar - - - ext/Tsk_DataModel.jar - release/modules/ext/Tsk_DataModel.jar - - - - + + + org.netbeans.modules.apisupport.project + + + org.sleuthkit.autopsy.core + + + + org.netbeans.api.progress + + + + 1 + 1.28.1 + + + + org.netbeans.core + + + + 2 + + + + + org.netbeans.core.startup + + + + 1 + + + + + org.netbeans.modules.javahelp + + + + 1 + 2.27.1 + + + + org.netbeans.modules.options.api + + + + 1 + 1.26.1 + + + + org.netbeans.modules.settings + + + + 1 + 1.35.1 + + + + org.netbeans.spi.quicksearch + + + + 1.14.1 + + + + org.netbeans.swing.outline + + + + 1.20.1 + + + + org.netbeans.swing.plaf + + + + 1.25.1 + + + + org.netbeans.swing.tabcontrol + + + + 1.36.1 + + + + org.openide.actions + + + + 6.26.1 + + + + org.openide.awt + + + + 7.46.1 + + + + org.openide.dialogs + + + + 7.25.1 + + + + org.openide.explorer + + + + 6.45.1 + + + + org.openide.filesystems + + + + 7.62.1 + + + + org.openide.modules + + + + 7.32.1 + + + + org.openide.nodes + + + + 7.28.1 + + + + org.openide.text + + + + 6.49.1 + + + + org.openide.util + + + + 8.25.1 + + + + org.openide.util.lookup + + + + 8.15.1 + + + + org.openide.windows + + + + 6.55.1 + + + + org.sleuthkit.autopsy.corelibs + + + + 3 + 1.0 + + + + + org.sleuthkit.autopsy.casemodule + org.sleuthkit.autopsy.casemodule.services + org.sleuthkit.autopsy.core + org.sleuthkit.autopsy.corecomponentinterfaces + org.sleuthkit.autopsy.corecomponents + org.sleuthkit.autopsy.coreutils + org.sleuthkit.autopsy.datamodel + org.sleuthkit.autopsy.directorytree + org.sleuthkit.autopsy.filesearch + org.sleuthkit.autopsy.ingest + org.sleuthkit.autopsy.menuactions + org.sleuthkit.autopsy.report + org.sleuthkit.datamodel + + + ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar + release/modules/ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar + + + ext/Tsk_DataModel.jar + release/modules/ext/Tsk_DataModel.jar + + + + diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index fcac88d7c3..55d119ec78 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -1,165 +1,165 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 Basis Technology Corp. - * Contact: carrier sleuthkit 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.core; - -import com.sun.javafx.application.PlatformImpl; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javafx.application.Platform; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.openide.modules.ModuleInstall; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; - -/** - * Wrapper over Installers in packages in Core module This is the main - * registered installer in the MANIFEST.MF - */ -public class Installer extends ModuleInstall { - - private List packageInstallers; - private static final Logger logger = Logger.getLogger(Installer.class.getName()); - private volatile boolean javaFxInit = true; - - public Installer() { - javaFxInit = true; - packageInstallers = new ArrayList(); - - packageInstallers.add(org.sleuthkit.autopsy.coreutils.Installer.getDefault()); - packageInstallers.add(org.sleuthkit.autopsy.corecomponents.Installer.getDefault()); - packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); - packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); - - } - - /** - * Check if JavaFx initialized - * @return false if java fx not initialized (classes coult not load), true if initialized - */ - public boolean isJavaFxInited() { - return this.javaFxInit; - } - - private void initJavaFx() { - //initialize java fx if exists - try { - Platform.setImplicitExit(false); - PlatformImpl.startup(new Runnable() { - @Override - public void run() { - logger.log(Level.INFO, "Initializing JavaFX for image viewing"); - } - }); - } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { - //in case javafx not present - javaFxInit = false; - final String msg = "Error initializing JavaFX. "; - final String details = " Some features will not be available. " - + " Check that you have the right JRE installed (Oracle JRE > 1.7.10). "; - logger.log(Level.SEVERE, msg - + details, e); - - WindowManager.getDefault().invokeWhenUIReady(new Runnable() { - @Override - public void run() { - MessageNotifyUtil.Notify.error(msg, details); - } - }); - - - } - - } - - @Override - public void restored() { - super.restored(); - - logger.log(Level.INFO, "restored()"); - - initJavaFx(); - - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " restored()"); - try { - mi.restored(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - - } - - @Override - public void validate() throws IllegalStateException { - super.validate(); - - logger.log(Level.INFO, "validate()"); - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " validate()"); - try { - mi.validate(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - } - - @Override - public void uninstalled() { - super.uninstalled(); - - logger.log(Level.INFO, "uninstalled()"); - - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " uninstalled()"); - try { - mi.uninstalled(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - - - - } - - @Override - public void close() { - super.close(); - - logger.log(Level.INFO, "close()"); - - //exit JavaFx plat - if (javaFxInit) { - Platform.exit(); - } - - for (ModuleInstall mi : packageInstallers) { - logger.log(Level.INFO, mi.getClass().getName() + " close()"); - try { - mi.close(); - } catch (Exception e) { - logger.log(Level.WARNING, "", e); - } - } - } -} +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier sleuthkit 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.core; + +import com.sun.javafx.application.PlatformImpl; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javafx.application.Platform; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.modules.ModuleInstall; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +/** + * Wrapper over Installers in packages in Core module This is the main + * registered installer in the MANIFEST.MF + */ +public class Installer extends ModuleInstall { + + private List packageInstallers; + private static final Logger logger = Logger.getLogger(Installer.class.getName()); + private volatile boolean javaFxInit = true; + + public Installer() { + javaFxInit = true; + packageInstallers = new ArrayList(); + + packageInstallers.add(org.sleuthkit.autopsy.coreutils.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.corecomponents.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); + + } + + /** + * Check if JavaFx initialized + * @return false if java fx not initialized (classes coult not load), true if initialized + */ + public boolean isJavaFxInited() { + return this.javaFxInit; + } + + private void initJavaFx() { + //initialize java fx if exists + try { + Platform.setImplicitExit(false); + PlatformImpl.startup(new Runnable() { + @Override + public void run() { + logger.log(Level.INFO, "Initializing JavaFX for image viewing"); + } + }); + } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { + //in case javafx not present + javaFxInit = false; + final String msg = "Error initializing JavaFX. "; + final String details = " Some features will not be available. " + + " Check that you have the right JRE installed (Oracle JRE > 1.7.10). "; + logger.log(Level.SEVERE, msg + + details, e); + + WindowManager.getDefault().invokeWhenUIReady(new Runnable() { + @Override + public void run() { + MessageNotifyUtil.Notify.error(msg, details); + } + }); + + + } + + } + + @Override + public void restored() { + super.restored(); + + logger.log(Level.INFO, "restored()"); + + initJavaFx(); + + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " restored()"); + try { + mi.restored(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + + } + + @Override + public void validate() throws IllegalStateException { + super.validate(); + + logger.log(Level.INFO, "validate()"); + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " validate()"); + try { + mi.validate(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + } + + @Override + public void uninstalled() { + super.uninstalled(); + + logger.log(Level.INFO, "uninstalled()"); + + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " uninstalled()"); + try { + mi.uninstalled(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + + + + } + + @Override + public void close() { + super.close(); + + logger.log(Level.INFO, "close()"); + + //exit JavaFx plat + if (javaFxInit) { + Platform.exit(); + } + + for (ModuleInstall mi : packageInstallers) { + logger.log(Level.INFO, mi.getClass().getName() + " close()"); + try { + mi.close(); + } catch (Exception e) { + logger.log(Level.WARNING, "", e); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form index 20985cd415..42f990a713 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form @@ -16,6 +16,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index a6f4b34cfe..35c289b54d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -65,7 +65,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo initComponents(); + // get the right panel for our platform videoPanel = MediaViewVideoPanel.createVideoPanel(); + imagePanel = new MediaViewImagePanel(); videoPanelInited = videoPanel.isInited(); imagePanelInited = imagePanel.isInited(); @@ -75,14 +77,14 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } private void customizeComponents() { - logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); //initialize supported image types //TODO use mime-types instead once we have support String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes(); IMAGES = new String[fxSupportedImagesSuffixes.length]; + //logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) { String suffix = fxSupportedImagesSuffixes[i]; - logger.log(Level.INFO, "suffix: " + suffix); + //logger.log(Level.INFO, "suffix: " + suffix); IMAGES[i] = "." + suffix; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java index e3faf121c6..88e8cc3105 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -1,648 +1,642 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 Basis Technology Corp. - * Contact: carrier sleuthkit 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.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.ArrayList; -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.Pane; -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.Exceptions; -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") - // - 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) - ); - }// - - // 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 { - - 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() { - @Override - public void changed(ObservableValue observable, Duration oldValue, Duration newValue) { - updateSlider(newValue); - updateTime(newValue); - } - }); - } - - private void setProgressActionListeners() { - pauseButton.setOnAction(new EventHandler() { - @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 captureFrames(java.io.File file, int numFrames) throws Exception { -// -// try { -// List 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 getFrames(int numFrames) { -// logger.log(Level.INFO, "in get frames"); -// List frames = new ArrayList(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; -// } -// } -} +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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") + // + 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) + ); + }// + + // 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 { + + 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() { + @Override + public void changed(ObservableValue observable, Duration oldValue, Duration newValue) { + updateSlider(newValue); + updateTime(newValue); + } + }); + } + + private void setProgressActionListeners() { + pauseButton.setOnAction(new EventHandler() { + @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 captureFrames(java.io.File file, int numFrames) throws Exception { +// +// try { +// List 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 getFrames(int numFrames) { +// logger.log(Level.INFO, "in get frames"); +// List frames = new ArrayList(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; +// } +// } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java index 2fdb28c45a..2914bc5282 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java @@ -1,766 +1,766 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 Basis Technology Corp. - * Contact: carrier sleuthkit 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 badVideoFiles = Collections.synchronizedSet(new HashSet()); - - /** - * 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 captureFrames(java.io.File file, int numFrames) throws Exception { - - List 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") - // - 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()) - ); - }// - - private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) { - 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(); - } - } - } - // 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 { - - 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 { - - 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(); - } - }); - } - } -} +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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 badVideoFiles = Collections.synchronizedSet(new HashSet()); + + /** + * 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 captureFrames(java.io.File file, int numFrames) throws Exception { + + List 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") + // + 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()) + ); + }// + + private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) { + 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(); + } + } + } + // 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 { + + 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 { + + 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(); + } + }); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index 15886f7293..b76724c08b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -1,117 +1,118 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 Basis Technology Corp. - * Contact: carrier sleuthkit 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.util.Arrays; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.JPanel; -import org.sleuthkit.datamodel.AbstractFile; - -/** - * Video viewer part of the Media View layered pane. - */ -public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture { - - private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); - - // 64 bit architectures - private static final String[] ARCH64 = new String[]{"amd64", "x86_64"}; - - // 32 bit architectures - private static final String[] ARCH32 = new String[]{"x86"}; - - // A Gstreamer implementation of MediaViewVideoPanel - private static GstVideoPanel gstVideoPanel = null; - - // A JavaFX implmentation of MediaViewVideoPanel - private static FXVideoPanel fxVideoPanel = null; - - /** - * Factory Method to create a MediaViewVideoPanel. - * - * Implementation is dependent on the architecture of the JVM. - * - * @return a MediaViewVideoPanel instance. - */ - public static MediaViewVideoPanel createVideoPanel() { - if (is64BitJVM()) { - logger.log(Level.INFO, "64 bit JVM detected. Creating JavaFX Video Player."); - return getFXImpl(); - } else { - logger.log(Level.INFO, "32 bit JVM detected. Creating GStreamer Video Player."); - return getGstImpl(); - } - } - - /** - * Is the JVM architecture 64 bit? - * - * @return - */ - private static boolean is64BitJVM() { - String arch = System.getProperty("os.arch"); - return Arrays.asList(ARCH64).contains(arch); - } - - /** - * Get a GStreamer video player implementation. - * - * @return a GstVideoPanel - */ - private static MediaViewVideoPanel getGstImpl() { - if (gstVideoPanel == null) { - gstVideoPanel = new GstVideoPanel(); - } - return gstVideoPanel; - } - - /** - * 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. - * - * @param file video file to play - * @param dims dimension of the parent window - */ - abstract void setupVideo(final AbstractFile file, final Dimension dims); -} +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JPanel; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Video viewer part of the Media View layered pane. + * Uses different engines depending on platform. + */ +public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture { + + private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); + + // 64 bit architectures + private static final String[] ARCH64 = new String[]{"amd64", "x86_64"}; + + // 32 bit architectures + private static final String[] ARCH32 = new String[]{"x86"}; + + // A Gstreamer implementation of MediaViewVideoPanel + private static GstVideoPanel gstVideoPanel = null; + + // A JavaFX implmentation of MediaViewVideoPanel + private static FXVideoPanel fxVideoPanel = null; + + /** + * Factory Method to create a MediaViewVideoPanel. + * + * Implementation is dependent on the architecture of the JVM. + * + * @return a MediaViewVideoPanel instance. + */ + public static MediaViewVideoPanel createVideoPanel() { + if (is64BitJVM()) { + logger.log(Level.INFO, "64 bit JVM detected. Creating JavaFX Video Player."); + return getFXImpl(); + } else { + logger.log(Level.INFO, "32 bit JVM detected. Creating GStreamer Video Player."); + return getGstImpl(); + } + } + + /** + * Is the JVM architecture 64 bit? + * + * @return + */ + private static boolean is64BitJVM() { + String arch = System.getProperty("os.arch"); + return Arrays.asList(ARCH64).contains(arch); + } + + /** + * Get a GStreamer video player implementation. + * + * @return a GstVideoPanel + */ + private static MediaViewVideoPanel getGstImpl() { + if (gstVideoPanel == null) { + gstVideoPanel = new GstVideoPanel(); + } + return gstVideoPanel; + } + + /** + * 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. + * + * @param file video file to play + * @param dims dimension of the parent window + */ + abstract void setupVideo(final AbstractFile file, final Dimension dims); +} diff --git a/NEWS.txt b/NEWS.txt index 178b8136fc..bae082a63e 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,784 +1,794 @@ ----------------- VERSION Current (development) -------------- - -New features: - - -Improvements: - - -Bugfixes: - - - ----------------- VERSION 3.0.6 -------------- - -New features: -- Logical files and folders support -- New file views in directory tree to view: deleted, executable, archive files and files by size -- ext4 and yaffs2 support (via TSK 4.1.0) - -Improvements: -- Improvements to tagging of files and keyword search results -- Any file and folder can be selectively ingested using the directory tree view - -Bugfixes: -- Keyword Search: fix when Solr does not cleanly shutdown -- fix for "Process Unallocated Space" option doesn't do anything -- fixed result viewer for "File Search by MD5 Hash" -- fix Solr, Timeline and RecentActivity issues with java 7.0.21 -- Views->Recent Files showing inconsistent results when clicked many times -- reduced memory usage in Timeline - - ----------------- VERSION 3.0.5 -------------- - -New features: -- Archive extractor ingest module (uses 7zip) -- Timeline (Beta) - -Improvements: -- Sleuthkit-4.0.2 and libewf-20130128 -- improved image loading in Media View and Thumbnail View (faster loading, handles large files better) -- improve Keyword Search file indexing (use detected mime-type instead of file extension) -- exif module - better jpeg detection using signature and not only file extension. -- show children counts in directory tree -- Ingest Message Inbox showing which messages are new better - -Bugfixes: -- fixed memory leaks in "Add Image" -- The "media view" tab is inactive for deleted files (#165) -- show error message in hex and string viewer if specific offset of a file could not be read. -- file search actions not always enabled when new case is open. -- fixed directory tree history being reset when tree is refreshed. - ----------------- VERSION 3.0.4 -------------- - -New features: -- Results and files can be tagged with custom tags and reported on them. -- New notification area for error reporting (bottom right). - -Improvements: -- Tweaked memory settings to eliminate out-of-memory errors. -- Faster application launch time. -- Netbeans RCP upgrade from 7.2.1 to 7.3 -- Upgrade from Java 6 to Java 7 - -Bugfixes: -- fixed DLL dependency version issue causing Autopsy not to launch on some systems -- fixed bug when keyword search ingest would search also images previously ingested, creating duplicate results -- fixed crash and hang in html and excel report generation, due to special characters present -- fixed cancellation when creating file or result bookmark -- fixed text not being extracted and searched from all MS Office documents (such as docx, xlsx and pptx extensions) -- fixed Exif meta-data extraction in Exif ingest module - - ----------------- VERSION 3.0.3 -------------- - -*Note: Due to major changes in Keyword search module indexing this release is not fully backward compatible. -As a workaround, you will need to rebuild index by re-running Keyword Search ingest on Cases created with previous versions. - -Improvements: -- Upgrade to Solr4.0 / Tika 1.2: Improved performance and highlighting -- Remake of reporting UI and functionality -- Significant increase in reporting speed -- New option to keep the most specific file viewer (default) or the lastly used viewer active. - - -Bugfixes: -- Fixed bug that caused the ends of large amounts of text to not be indexed (occurs mostly in unallocated space). -- Fix scrolling to first keyword hit when Text View is first loaded -- Imported keyword lists are now always enabled for ingest by default - - ----------------- VERSION 3.0.2 -------------- - -New features: -- Extraction of all unallocated blocks as a single file -- Results bookmarks with comments and basic bookmark reporting -- Hashkeeper hash database support - -Improvements: -- File Ingest: minimized file queuing time and memory usage, also improving ingest stability -- Jump to arbitrary page in Thumbnail View -- Add Image Wizard - better work-flow, better device size reporting, info on currently processed directory -- Reporting: reorganized columns, sorted by 1st column, added logo, better styling - -Bugfixes: -- fixed periodic keyword search during ingest, when it would only search max. 2 times -- fixed Downloads "target" in Recent Activity -- fixed missing hash and keyword search hits in reports -- fixed deselecting NSRL database for hash ingest - - ----------------- VERSION 3.0.1 -------------- - -New features: -- Physical and logical disk devices discovery in Add image wizard - -Improvements: -- Significant performance improvements when adding images. -- Slight improvements in UI performance for large number of results. -- Improved stability when running ingest on multiple images. -- Removed limit on number of results displayed. -- Thumbnail viewer - added paging and removed limit of images. -- Better HTML report navigation, handling large reports better. -- Netbeans RCP upgrade from 7.2 to 7.2.1 -- Build scripts enhancements to include module version tracking. - -Bugfixes: -- Fixed reading content from multiple file attributes (NTFS, HFS). -- Add Extract action to Unalloc content file nodes (per file). -- Fixes bugs with case re-opening. -- UI fix for keyword search box when case is changed. -- Enable user to select any image file extension when opening image. -- Thunderbird parser module fixes. -- Reporting fixes: added missing artifacts (keyword search, hash hits, file bookmarks). - - ----------------- VERSION 3.0.0 -------------- -New features: -- Using Sleuthkit 4.0.0 -- Integrated plugin installer. -- New options menu to globally access module options. -- Added custom ingest module loader and ingest module auto-discovery - -Improvements: -- Updated ingest framework APIs. -- Merged the main modules into Autopsy-Core and Autopsy-CoreLibs. -- Improved logging infrastructure. -- Improved configuration infrastructure. -- Keyword search: upgraded Lucene from 34 to 36. -- Build system improvements. -- Updated documentation. - -Bugfixes: -- UI selection fix in Content and Result viewer -- UI fixes in Hash Database and Keyword Search options. -- Excel report export produced corrupt files sometimes. -- Fix for Keyword Search sometimes not property initializing when application starts. - -3.0.0b5 (September 12, 2012) -New features: -- Added international string extraction from unknown file types. -- Removed size limitations of large files for keyword searching. -- Added full html parsing and extraction (including comments, scripts, meta tags, etc). -- Added support for indexing and searching disk images that have no volume and file system. -- Solr (3.6.1) and Tika (1.0) upgrade. -- Search a file by hash GUI feature and search other files with same hash. -- Web search query text extraction from popular search engines. -- Exif metadata extraction from jpeg files. -- Netbeans RCP platform upgrade (7.2). -- Basic file bookmarks support. -- Body file report. -- Improved UI. -- Updated Ingest Module API. - -Bugfixes: -- Keyword search memory usage improvements. -- Directory tree now shows which directories have no children before user clicks. -- Fixed bug when recent cases would not get updated. -- Fixed a bug when sometimes a case would get deleted. -- Fixed occasional Media View crashes. - -3.0.0b4 (June 29, 2012) -Funded by US Army Intelligence Center of Excellence (USAICoE): -New Features: -- MBOX parsing -- Better lnk file parsing -Bug Fixes: -- Included needed jar file for Recent Activity (Issue #52). -- Fixed error handling from ingest (Issue #53). - -3.0.0b3 (June 12, 2012) -New Features (Funded by US Army Intelligence Center of Excellence (USAICoE)): -- Ingest manager runs triage/ingest task after disk is added. -- Basic keyword search (indexed via SOLR) -- Recent activity extract (web artifacts, recent documents, devices, etc.) -- Improved UI - -3.0.0b2 (Nov 9, 2011) -New Features: -- New database design -- Hashlookup / calculation -- Minor overall improvements -- NOTE: Cases created with b1 are not supported in b2 (different DB) - -3.0.0b1 (Aug 16, 2011) -- Initial release -- Windows only -- Directory tree -- File Search -- Table and thumbnail viewer - ---------------------------- Version 2.24 -------------------------------- -3/22/10: Bug Fix: resolved issue 2950986 to support HFS directories. - ---------------------------- Version 2.23 -------------------------------- -2/12/10: bug fix: resolved issue 2950693 where previous searches -were not shown if they used quotes. - -2/12/10: bug fix: resolved issue 2932385 where wrong flag was being used -to do only doing category searching" - -2/12/10: bug fix: resolved issue 2779244 where wrong sorter path was -being used. - ---------------------------- Version 2.22 -------------------------------- -10/27/09: Update: Change istat to use -B instead of -b (new change in TSK). - -11/19/09: Update: Improved configure script process and error message for -FILE_EXE check. - -11/25/09: Fixed MD5 exe bug when building live CD - -12/30/09: Fixed issue 2923857 re: cookie errors for the icon and css file -links when cookies are used. - ---------------------------- Version 2.21 -------------------------------- -11/7/08: Bug Fix: Changed case management code to not error when 'dls ...' -line was encountered. - -11/14/08: Bug Fix: Fixed bug 2288406 (parsing of new fls -l format when file name searching and deleted file listing) - ---------------------------- Version 2.20 -------------------------------- -7/1/08: Update: Updated FAT sizes based on new "special" files. - -7/9/08: Update: Updated NTFS processing for orphan files / removed -ifind -p etc. - -7/9/08: Update: Updated mactime and time formats to ISO formats. - -9/13/08: Update: Changed usage to new TSK d* to blk* names. - -9/26/08: Bug Fix: Input check on host was printing invalid host values -w/out encoding HTML entities. Reported by Russ McRee. - -10/01/08: Update: HFS support is enabled if TSK was compiled with -support for it. - -10/08/08: Bug Fix: Added some more HTML entity escaping to case management -values (such as description). Reported by Daniel Medianero. - -10/13/08: Update: Added perl version check back into configure, but used -perl $] variable to do checking. Based on patch by Joerg Friedrich. - ---------------------------- Version 2.10 -------------------------------- -2/20/08: Bug Fix: Added 'tsk' to the path for sorter to find the 'images' -config file. Reported by Russell Reynolds. - -3/2/08: Update: Modified the adding of disk image process to save a -call to mmls (reported by Pope). - -3/2/08: Update: Added more basic control char filtering back into Print(). - ---------------------------- Version 2.09 -------------------------------- -2/4/07: Update: Bind only to localhost network if remote addr is local. -Suggested by Markus Waldeck. - -4/19/07: Bug Fix: Event sequencer notes for file did not have clock skew -in the times. Reported by Len CulBreath. - -12/21/07: Update: updated configure and install process for TSK 2.50 - -1/28/08: Update: Added NSRL support back in. - ---------------------------- Version 2.08 -------------------------------- - -8/23/06: Bug Fix: The configure script did not like TSK directory names -with a space in them. - -8/23/06: Update: The PATH variable is not entirely cleared anymore. -Instead, it is replaced by the basic bin directories (this was causing -some problems with Cygwin). - -8/31/06: Update: If Autopsy is running under Cygwin, then it will set -the PATH to contain the basic bin directories. Otherwise, it is clear -(original behavior). - - ---------------------------- Version 2.07 -------------------------------- -3/15/06: Bug Fix: Caseman.pm had DATA_DIR instead of DATADIR and a -concatenation error message occurred. Reported by Jason DePriest. - -5/3/06: Update: Added support for ISO9660 file systems. - -5/3/06: Update: Added support for AFF and AFD image formats. - -5/03/06: Update: Added image format type to image details screen. - -5/3/06: Update: Added hexdump view for file analysis and reports (initial -patch by Patrick Knight). - -5/3/06: Update: Changed number of dashes in reports to 70 instead of 62. - -5/4/06: Update: Integrity checking disabled for non-raw image files -until a specialized tool exists in TSK to abstract the embedded hash -calculation. - -5/8/06: Update: Added support for AFM files. - - - ---------------------------- Version 2.06 -------------------------------- -05/02/05: Fix: Typo in timeline creation window (reported by Surago Jones). - -06/15/05: Update: Added css style sheet and changed some formatting. - -08/13/05: Update: Added "utf-8" as HTML type so that TSK unicode -output will be properly dispayed. - -10/13/05: Update: Removed print_output() function contents because -it broke the Unicode chars. - -10/13/05: Update: Require 5.8 version of Perl now (in config and -in source) because it has best Unicode support. - - - ---------------------------- Version 2.05 -------------------------------- -03/16/05: Update: Image name is given in the Image Details window -when adding a new image file. (Suggested by Surago Jones). - -03/17/05: Bug Fix: swap and raw host config entries could not be -read after the conversion because of a regular expression bug in -the read code. (Reported by Surago Jones) (BUG: 1165235) - -03/21/05: Bug Fix: When a new host was added to a case with no -investigator names, then it would prompt you to select a name from -an empty list. (BUG: 1167970). - -03/25/05: Update: Check return status of rename functions and print -error if failed. - -04/04/05: Bug Fix: A missing volume type message was reported when -adding a disk image. The flow of add_img_prep was modified to -ensure that it was set. (Reported by Bradley Bitzkowski) (BUG: -1177042) - -04/08/05: Update: A thumbnail of images is shown when selected in the File -mode. Suggested by and patch by Guy Voncken. - - ---------------------------- Version 2.04 -------------------------------- -10/22/04: Update: Changed the way that NTFS lists directory contents. No - longer lists the deleted entries from 'fls', only from 'ifind'. Reduces - the inaccurate information. - -02/XX/05: Update: Incorporated new TSK 2 features: - - Disk images (split and raw) - - new config file formats - - moved images and output md5.txt file into one - -03/01/05: Update: Changed behavior of some links that created new -Autopsy Windows - -03/05/05: Update: timeline output can be in comma delimited format - -03/05/05: Update: Added SSN and credit card seach patterns from - Jerry Shenk. - -03/05/05: Update: Added temporal data when a note is creaed. - -03/11/05: Update: Changed to new TSK names for srch_strings and img_stat - -03/15/05: Update: improved handling of white space around investigator -names and image names (suggested by Brian Baskin). - - ---------------------------- Version 2.03 -------------------------------- -08/24/04: Update: Added SHA-1 hash to the metadata view. - -09/01/04: Update: Added sstrings instead of local version of strings. - -09/05/04: Update: Added more help text. - -09/06/04: Update: Use the local version of file if TSK version is -not found. - -09/06/04: Update: Added links to the notes and events page after a -note or event has been created. - -09/06/04: Update: Added Unicode extract and search functionality using -the 'sstrings' tool from TSK. - - ---------------------------- Version 2.02 -------------------------------- -07/19/04: Bug Fix: print_err message in Caseman.lib did not have correct -Print:: package, which caused an error (BUG: 994199). - -07/29/04: Update: Added support for NTFS 'ifind -p' option to find deleted -files that do not have a name in the parent directory. - -07/29/04: Update: Added a filter to remove duplicate entries from a file -listing. Duplicate names with the same name and meta address are -removed. - -07/29/04: Update: OS X no longer needs the strings script, Autopsy -will adjust for the different flags. - -07/29/04: Update: When a deleted file name is entered into the find -directory box, the recover bit is set so the full contents are shown. - - ---------------------------- Version 2.01 -------------------------------- -03/29/04: Update: Changed text for the data integrity option when -adding a new image. - -04/20/04: Bug Fix: Fixed error that occured when data browsing with -a raw or swap image. The TSK usage for these file system types was -inconsistent and it was fixed in version 1.69. (BUG: 925382). -(Reported by Harald Katzer) - -05/03/04: Update: Changed regular expression in META so that the -new recovery listing in FAT istat will not show up as a hyperlink. - -05/03/04: Update: Removed usage of '-H' with 'icat' in File.PM. - -05/20/04: Bug Fix: Fixed the incorrect error message that was printed -when installing autopsy with a newer version of TSK than 1.68. -(BUG: 938909) - -05/20/04: Update: Added new feature that allows perl regular -expressions to be used to find file names. - -05/20/04: Update: Added file recovery features to File.pm, Meta.pm, -and Appview.pm. - -05/27/04: Update: Added a space to $REG_ZONE2 so that CYGWIN would -work if no zone was given (Marcus Muller). - -/05/27/04: Update: Added 'p' as an option for the type of a file in the -'fls' output and made the $::REG_MTYPE global for the pattern. - -05/28/04: Update: Cleaned up code so that commands and directories -do not have double slashes (//) sometimes. This caused problems -with CYGWIN (reported by Marcus Muller). - -05/28/04: Bug Fix: Keyword search of unallocated space would link to -incorrect data unit (although the address was correct). (Reported by -Jorge Ortiz, David Perez, Raul Siles). (BUG: 962410). - -05/28/04: Update: Updated dcat usage and syntax to reflect changes to -TSK. - -05/28/04: Update: Changed the messages printed when multiple data units -were displayed. Now the number of units or range are given instead of -number of bytes. - - ---------------------------- Version 2.00 -------------------------------- -11/25/03: Update: made evidence locker directory names constant (define.pl) -11/25/03: Update: Started process of re-architecture -12/2/03: Update: Replaced logo.jpg with Hash the Hound -12/7/03: Update: Added favicon.ico with Hash -01/06/04: Update: Changed command line arguments -01/24/04: Update: made it only a warning if cookie file can't be opened -02/15/04: Update: Timezone is now optional. Defaults to local if not given. -02/15/04: Update: Timezone value optional in () in file listing (prevents - parsing errors if incorrect timezone is given -03/16/04: Bug Fix: Fixed zombie problem by ignoring child signal -(BUG: 860186) Reported by Angus Marshall. -03/18/04: Update: New layout for adding cases, hosts, and images. -03/18/04: Update: changed HTML to use lowercase values instead of all caps. -03/18/04: Update: New windows are no longer opened when changing modes. -03/19/04: Release: Big release with a new redesign and a few other - changes (live analysis) - ---------------------------- Version 1.75 -------------------------------- -09/22/03: Update: Changed the internal 'get_' functions that parse the - URL arguments to error instead of just return 0 when a problem occurs. -10/22/03: Bug Fix: Check for an investigator name before trying to log - to the exec log. This is a problem when indexing a hash database, an - error message is printed because of the null string. reported by - Brian Baskin. -11/10/03: Update: Improved error message when strings can't be parsed. - (Bug: 823081) -11/15/03: Update: Improved messages in installation script -11/15/03: Bug Fix: Added 'defined' checks to command output to prevent - string errors when command fails. (BUG 842824) -11/15/03: Update: Added 'HEIGHT' value to HTML images to make images - align better and load faster and with the right size -11/15/03: Update: Added a timer so that a char is printed every 5 seconds - during keyword searching, file type sorting, and MD5 for images. - ---------------------------- Version 1.74 -------------------------------- -08/03/03: Bug Fix: Notes could not be added for some files because - the HTML code was missing a closing bracket. -08/18/03: Bug Fix: added POSIX:settz() because some versions of Perl do - not use the most recent ENV{TZ} variable when running 'localtime'. This - cause some incorrect times for events in the sequencer. -08/19/03: Update: NSRL is no longer used with 'sorter' until it is - easier to identify which files in the NSRL are known good and which - are known bad. -08/20/03: Update: Added support for swap and raw images for searching - and data unit analysis. -08/20/03: Update: Added the unit size to the display of the Data Unit - mode. -08/20/03: Update: Search for perl5.6.0 first during install -08/21/03: Update: Changed use of backticks to pipes for executing commands -08/21/03: ?: Added a 'sleep(1)' to the pipe to prevent the loss of data - that can be seen with perl5.8.0 in the buffer. This should be fixed - in a better way though. -08/21/03: Update: The exact command executed is now saved to the log - directory. -08/21/03: Update: Changed 'date' regexp to make year optional. -08/22/03: Update: Added warning if Perl 5.8 is used because of the buffer - problem. -08/22/03: Bug Fix: Fixed some keyword escape values in the search mode. -08/22/03: Update: Added a new help page on the limitations of keyword - searching. -08/22/03: Update: Moved the unallocated space and strings file creation - to the Image Details view instead of the keyword search window - (suggested by: Paul Bakker) -08/25/03: Update: improved wording of the Add Image window to better - explain the mounting point. -08/26/03: Update: When adding sequencer notes in manually, the time - is set to the last note entered to make it easier to add notes from - logs and external sources. -08/26/03: Update: The keyword search display has a final clause that - prints the results even if they are not found in the 'index' method. - This prevents any hits from being lost during the analysis of the - output. -08/26/03: Bug Fix: strings less than 4 chars would not be found before - because 'strings' only shows strings that are 4 or more in length -08/28/03: Update: if more than 1000 keyword hits are found, then a message - is reported and the user must choose a new keyword. This prevents the - browser from hanging from a huge HTML table. -08/28/03: Update: A '.' is printed during the keyword search for each - 100 hits as a status update. - - ---------------------------- Version 1.73 -------------------------------- -06/10/03: Bug Fix: The '-i day' was not added to the mactime code and - caused an error (reported by Cathy Buckman) - ---------------------------- Version 1.72 --------------------------------- -04/09/03: Bug Fix: The Java Script check on the main page broke in 1.71 - because the document.write was on multiple lines -04/11/03: Bug Fix: Keyword Search False Hit code had a bug that it - would be printed in error and message was improved -04/22/03: Update: Added examples to case management help file -05/06/03: Bug Fix: calc_md5 did not need 'o' tag on end of regular - expression because it would not work if the method was called more - than once. (Paul Bakker) -06/01/03: Bug Fix: Some keyword searches with $ in it were failing -06/01/03: Update: Keyword searches are now saved to a file and can be - found in the keyword seach main menu -06/01/03: Update: Changed the format a little of the keyword search - menu -06/01/03: Update: Added grep cheat sheet -06/03/03: Update: Tables now have alternating colors for file listing - and timeline viewing -06/03/03: Update: Sequencer mode added -06/03/03: Update: Sequencer help file added -06/04/03: Bug Fix: Added 'LANG=C LC_ALL=C' to sorter & mactime to prevent - UTF-8 errors (Debugging help from Daniel Schwartzer) -06/04/03: Bug Fix: The regular expression for viewing timelines did not - allow multiple users to have the same UID (reported by Cathy Buckman) -06/05/03: Update: Added button for Event Sequencer and added tables to - the standard notes reading window -06/09/03: Update: Added '-i day' flag to mactime for new feature in - The Sleuth Kit - ---------------------------- Version 1.71 --------------------------------- -02/27/03: Bug Fix: Regular expression searches w/out a strings file had - problems because the '-n' value was being incorrectly calculated. -03/17/03: Update: Added more logging to investigator log -03/17/03: Bug Fix: The case opening was not being logged in the case log -03/17/03: Update: The current 'mode' tab is also a hyperlink now -03/17/03: Bug Fix: Fixed bug that did not allow the path for a strings - file to have a space in it. -03/17/03: Update: When no port and remote address are given on the - command line, port 9999 and localhost are used. Documents also - updated to reflect new syntax. -03/18/03: Update: Use the 'x' repetition operator for ASCII reports - instead of a row of dashes. -03/18/03: Update: Added tag to MAIN_FR and incorporated more - '<<EOF' HTML code. -03/19/03: Update: Added $FIL_NAME function that translates a name to - a meta data address using 'ifind -n' -03/19/03: Update: A directory name can be entered in the $FIL_DIR - frame now to jump to a directory or file -03/19/03: Update: The directory path in $FIL_LIST was changed to have - hyperlinks that allow one to jump to a previous directory (using - $FILE_NAME) -03/19/03: Update: Cleaned up HTML code in $FIL_LIST -03/20/03: Update: passwd and group files are now imported in timelines - by selecting the image - no more inode values -03/20/03: Update: Cleaned up HTML code in timeline section -03/21/03: Update: Added '-z' flag to usage of 'file' so that comressed - files are opened. -03/21/03: Bug Fix: Some special values needed to be escaped in the - grep keyword search (for non regular expressions) (\.]^$"-). -03/24/03: Update: Changed how images are added (symlinks, copies, - or moves). -03/24/03: Update: Added a file system sanity check when adding one -03/27/03: Update: Added a check to the 'File Type' mode that extracts - just graphic images and makes thumbnails. -03/27/03: Update: Added '-i' flag when 'mactime' is run to create the - summary file for timelines. -03/27/03: Update: Added link to summary page with hyper links to actual - month for timelines -03/27/03: Update: Added more HTML table columns for date in timeline view -03/27/03: Update: Made the 'ifind' process optional in Data Unit and key - word searching mode (makes browsing faster) -03/27/03: Update: Evidence Locker now contains entries for when a case - is created or opened. -03/30/03: Update: Improved the help file for time lines. -03/31/03: Update: Changed addresses to sleuthkit.org - - - ---------------------------- Version 1.70 --------------------------------- -Interface Changes: - - Too many to note individually - - New windows are created when modes or images are changed - - Improved error messages - - Can load the unallocated image in the Data Unit Mode - - Case management - -12/10/02: Update: Help is now a directory and contents can be viewed at - any time. -01/02/03: Update: Added support for sorter and hfind tools in TASK -01/02/03: Update: NSRL now requested at startup -01/02/03: Update: Alert and exclude hash databases are options when making - a new host now -01/09/03: Update: Carriage Returns are now sent if it is a Windows client -01/09/03: Update: Improved the pre-defined IP keyword search expression -01/10/03: Update: Changed use of "_new" as target to "_blank" -01/28/03: Update: Installation and other system directories can now - have spaces and other symbols in them (Dave Goldsmith) - - ---------------------------- Version 1.62 --------------------------------- -10/07/02: Update: Added File Type to block mode -10/07/02: Update: Can now add notes to 'dls' image blocks -10/07/02: Update: One can now view as many consecutive data units as they - want in data mode. Many other changes and updates were done with this - as well. (inspired by the Honeynet sotm) -10/07/02: Update: The File System details view for FAT now has hyperlinks - to view the run and follow to the next run. -10/09/02: Bug Fix: Removed use of 'use integer' so that large blocks do - no turn into '-1' when doing a keyword search (Michael Stone - Loyola) - - ---------------------------- Version 1.61 --------------------------------- -08/28/02: Update: White space is allowed at the begining of the morgue file -08/28/02: Bug Fix: No error is generated if md5.txt does not exist from - main menu -08/28/02: Update: Improved error messages -08/28/02: Update: Added code to Main Menu to check for Java Script turned on -09/19/02: Update: fsmorgue can be a symlink in the morgue directory - - ---------------------------- Version 1.60 --------------------------------- -- Changed NTFS c-time to Changed from Created (5/20/02) -- Fixed a couple little bugs with parsing NTFS output (5/20/02) -- Improved sorting (name is case insensitive and name is used as - secondary sorting index) (5/20/02) -- Improved error messages of invalid input to inode & block mode -- Added ability to import password and group files when making a time line - (5/28/02) -- Fixed bug that did not allow IP addresses to be used for the ACL when - DNS was not available (5/30/02) -- Fixed some issues to make Internet Explorer not complain so much (05/30/02) -- Improved the logging so that one can retrace their actions (05/31/02) -- Moved autopsy.log to logs directory (05/31/02) -- Added ability to write Notes about a given block, inode, or file (06/04/02) - (suggestion by Dave Dittrich) -- Set default investigators name (an error was generated if no name was given) - (06/04/02) -- Added links in the help page to the window help pages (06/05/02) -- Updated timeline to reflect new format in new TASK (06/19/02) -- Added '-C' flag to turn off cookies on command line (06/20/02) -- Added new main menu (06/20/02) -- Made MD5 generation 'opt-out' (06/22/02) -- New code to remove duplicate entries in md5.txt and fsmorgue -- fsmorgue can have whitespace at end of line (7/6/02) -- An error is generated if an image in fsmorgue does not exist (7/6/02) -- updated automatic date search (7/9/02) -- New feature allows one to save the MD5 values of all files in a directory, - which makes the Solaris Finger Print Database easier (7/12) - - ---------------------------- Version 1.50 --------------------------------- -- Modified to support TASK instead of TCT and TCTUTILs (8/25/01) -- Removed chmod 'bug' for the cookie file (8/25/01) -- Fixed number of hits bug in Search mode (off by one) (8/25/01) -- Added ftype support (8/28/01) -- Added ftype field to reports (8/28/01) -- Encoded dir arg in FIL_DEL -- Filter option holds for usage of next and rev in block mode -- If using fat, a separate option is given to run find_inode due to how - slow it runs -- removed use of zoneinfo in favor of the new timezone value in fsmorgue. -- strings now uses '-a' flag to show all strings -- When doing a search, the length of the string is given as the '-n' - flag to strings to speed up the search -- Allow user to "force" blocks when an inode size is 0 (the istat -b flag) -- use the md5 that comes with TCT/TASK -- multiple images with the same mounting point can now exist -- Added the morgue directory to the MENU to make it easier to manage - multiple hosts -- Files are sorted by name by default -- can import strings files and create them if needed -- Run files through 'file' to get data type -- case insensitive searches -- MAC headers correspond to file system type (create vs change) -- Deleted files are displayed in red -- Correct address name used (fragment, sector etc.) -- Support for NTFS attributes -- parse bad tags from HTML when viewing it (send sterile pict) -- cookie file has port number to aid in scripting -- cookie files are deleted upon closing -- log messages are printed for each request -- added integrity checker -- renamed aux directory to base to make Windows happy -- added time line support -- added fsstat support -- Added built-in search values in search.pl - - -May 29, 2001 1.01 released -- Fixed Hex link when in search mode (3/23/01) -- Corrected heading of ctime (Addam Schroll, Purdue University) (4/24/01) -- Parses output of new istat correctly (5/1/01) -- When viewing 'inode as a file', the image and inode are sent as the dir - name (5/1/01) -- Added wait() to collect zombies in Linux (5/22/01) -- Added auto-flush to prevent repeat log entries (5/22/01) -- Added a 'save as' option to file and inode browsing (Addam Schroll) - (5/22/01) -- Added option for unrm block numbers (due to blockcalc) (5/22/01) -- Improved side menu for inode, block, and search (5/22/01) -- Added "Content-Disposition" so that reports and "save as" have a - unique default filename. (5/23/01) -- Organization changes to Main Menu (5/24/01) -- Automated installation process (5/24/01) - -March 19, 2001 1.0 released -- Added man page for autopsy (3/10/01) -- Directory entries in config files no longer require an / at the end -- Morgue file names can have a '.' in them (but still not '/') (3/10) -- autopsy first checks for /dev/urandom for random cookie (3/10/01) -- morgue directory is a command line option to autopsy (3/10/01) -- the lib variable in autopsy is no longer set to './' so that it - can be run outside of /usr/local/autopsy (3/10/01) -- changed all references of device to image (3/11/01) -- changed all reports to print full image path (3/11/01) -- Investigator is a command line option to autopsy (3/11/01) -- CGI support removed. Only autopsy is supported (3/16/01) -- renamed autopsyd to autopsy (3/16/01) -- Fixed UID and GID heading (3/16/01) -- Run image through strings before grep to prevent memory errors (3/16/01) -- output of find_file and find_inode is prepended with rdir (3/16/01) - - -Feb 27, 2001 0.2b released -- Added stand alone server, autopsyd (as suggested by Dan Farmer) -- Reorganized files due to new program -- Changed names of some executables that changed in TCTUTILs - -Feb 19, 2001 0.1b released - ------------------------------------------------------------------------- +---------------- VERSION Current (development) -------------- + +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: +- EXIF module uses only signatures +- File size view does not show unalloc files +- Tagged files in report show more data +- Updated test scripts + + +Bugfixes: +- Several -> Didn't keep good track in this file. + + + +---------------- VERSION 3.0.6 -------------- + +New features: +- Logical files and folders support +- New file views in directory tree to view: deleted, executable, archive files and files by size +- ext4 and yaffs2 support (via TSK 4.1.0) + +Improvements: +- Improvements to tagging of files and keyword search results +- Any file and folder can be selectively ingested using the directory tree view + +Bugfixes: +- Keyword Search: fix when Solr does not cleanly shutdown +- fix for "Process Unallocated Space" option doesn't do anything +- fixed result viewer for "File Search by MD5 Hash" +- fix Solr, Timeline and RecentActivity issues with java 7.0.21 +- Views->Recent Files showing inconsistent results when clicked many times +- reduced memory usage in Timeline + + +---------------- VERSION 3.0.5 -------------- + +New features: +- Archive extractor ingest module (uses 7zip) +- Timeline (Beta) + +Improvements: +- Sleuthkit-4.0.2 and libewf-20130128 +- improved image loading in Media View and Thumbnail View (faster loading, handles large files better) +- improve Keyword Search file indexing (use detected mime-type instead of file extension) +- exif module - better jpeg detection using signature and not only file extension. +- show children counts in directory tree +- Ingest Message Inbox showing which messages are new better + +Bugfixes: +- fixed memory leaks in "Add Image" +- The "media view" tab is inactive for deleted files (#165) +- show error message in hex and string viewer if specific offset of a file could not be read. +- file search actions not always enabled when new case is open. +- fixed directory tree history being reset when tree is refreshed. + +---------------- VERSION 3.0.4 -------------- + +New features: +- Results and files can be tagged with custom tags and reported on them. +- New notification area for error reporting (bottom right). + +Improvements: +- Tweaked memory settings to eliminate out-of-memory errors. +- Faster application launch time. +- Netbeans RCP upgrade from 7.2.1 to 7.3 +- Upgrade from Java 6 to Java 7 + +Bugfixes: +- fixed DLL dependency version issue causing Autopsy not to launch on some systems +- fixed bug when keyword search ingest would search also images previously ingested, creating duplicate results +- fixed crash and hang in html and excel report generation, due to special characters present +- fixed cancellation when creating file or result bookmark +- fixed text not being extracted and searched from all MS Office documents (such as docx, xlsx and pptx extensions) +- fixed Exif meta-data extraction in Exif ingest module + + +---------------- VERSION 3.0.3 -------------- + +*Note: Due to major changes in Keyword search module indexing this release is not fully backward compatible. +As a workaround, you will need to rebuild index by re-running Keyword Search ingest on Cases created with previous versions. + +Improvements: +- Upgrade to Solr4.0 / Tika 1.2: Improved performance and highlighting +- Remake of reporting UI and functionality +- Significant increase in reporting speed +- New option to keep the most specific file viewer (default) or the lastly used viewer active. + + +Bugfixes: +- Fixed bug that caused the ends of large amounts of text to not be indexed (occurs mostly in unallocated space). +- Fix scrolling to first keyword hit when Text View is first loaded +- Imported keyword lists are now always enabled for ingest by default + + +---------------- VERSION 3.0.2 -------------- + +New features: +- Extraction of all unallocated blocks as a single file +- Results bookmarks with comments and basic bookmark reporting +- Hashkeeper hash database support + +Improvements: +- File Ingest: minimized file queuing time and memory usage, also improving ingest stability +- Jump to arbitrary page in Thumbnail View +- Add Image Wizard - better work-flow, better device size reporting, info on currently processed directory +- Reporting: reorganized columns, sorted by 1st column, added logo, better styling + +Bugfixes: +- fixed periodic keyword search during ingest, when it would only search max. 2 times +- fixed Downloads "target" in Recent Activity +- fixed missing hash and keyword search hits in reports +- fixed deselecting NSRL database for hash ingest + + +---------------- VERSION 3.0.1 -------------- + +New features: +- Physical and logical disk devices discovery in Add image wizard + +Improvements: +- Significant performance improvements when adding images. +- Slight improvements in UI performance for large number of results. +- Improved stability when running ingest on multiple images. +- Removed limit on number of results displayed. +- Thumbnail viewer - added paging and removed limit of images. +- Better HTML report navigation, handling large reports better. +- Netbeans RCP upgrade from 7.2 to 7.2.1 +- Build scripts enhancements to include module version tracking. + +Bugfixes: +- Fixed reading content from multiple file attributes (NTFS, HFS). +- Add Extract action to Unalloc content file nodes (per file). +- Fixes bugs with case re-opening. +- UI fix for keyword search box when case is changed. +- Enable user to select any image file extension when opening image. +- Thunderbird parser module fixes. +- Reporting fixes: added missing artifacts (keyword search, hash hits, file bookmarks). + + +---------------- VERSION 3.0.0 -------------- +New features: +- Using Sleuthkit 4.0.0 +- Integrated plugin installer. +- New options menu to globally access module options. +- Added custom ingest module loader and ingest module auto-discovery + +Improvements: +- Updated ingest framework APIs. +- Merged the main modules into Autopsy-Core and Autopsy-CoreLibs. +- Improved logging infrastructure. +- Improved configuration infrastructure. +- Keyword search: upgraded Lucene from 34 to 36. +- Build system improvements. +- Updated documentation. + +Bugfixes: +- UI selection fix in Content and Result viewer +- UI fixes in Hash Database and Keyword Search options. +- Excel report export produced corrupt files sometimes. +- Fix for Keyword Search sometimes not property initializing when application starts. + +3.0.0b5 (September 12, 2012) +New features: +- Added international string extraction from unknown file types. +- Removed size limitations of large files for keyword searching. +- Added full html parsing and extraction (including comments, scripts, meta tags, etc). +- Added support for indexing and searching disk images that have no volume and file system. +- Solr (3.6.1) and Tika (1.0) upgrade. +- Search a file by hash GUI feature and search other files with same hash. +- Web search query text extraction from popular search engines. +- Exif metadata extraction from jpeg files. +- Netbeans RCP platform upgrade (7.2). +- Basic file bookmarks support. +- Body file report. +- Improved UI. +- Updated Ingest Module API. + +Bugfixes: +- Keyword search memory usage improvements. +- Directory tree now shows which directories have no children before user clicks. +- Fixed bug when recent cases would not get updated. +- Fixed a bug when sometimes a case would get deleted. +- Fixed occasional Media View crashes. + +3.0.0b4 (June 29, 2012) +Funded by US Army Intelligence Center of Excellence (USAICoE): +New Features: +- MBOX parsing +- Better lnk file parsing +Bug Fixes: +- Included needed jar file for Recent Activity (Issue #52). +- Fixed error handling from ingest (Issue #53). + +3.0.0b3 (June 12, 2012) +New Features (Funded by US Army Intelligence Center of Excellence (USAICoE)): +- Ingest manager runs triage/ingest task after disk is added. +- Basic keyword search (indexed via SOLR) +- Recent activity extract (web artifacts, recent documents, devices, etc.) +- Improved UI + +3.0.0b2 (Nov 9, 2011) +New Features: +- New database design +- Hashlookup / calculation +- Minor overall improvements +- NOTE: Cases created with b1 are not supported in b2 (different DB) + +3.0.0b1 (Aug 16, 2011) +- Initial release +- Windows only +- Directory tree +- File Search +- Table and thumbnail viewer + +--------------------------- Version 2.24 -------------------------------- +3/22/10: Bug Fix: resolved issue 2950986 to support HFS directories. + +--------------------------- Version 2.23 -------------------------------- +2/12/10: bug fix: resolved issue 2950693 where previous searches +were not shown if they used quotes. + +2/12/10: bug fix: resolved issue 2932385 where wrong flag was being used +to do only doing category searching" + +2/12/10: bug fix: resolved issue 2779244 where wrong sorter path was +being used. + +--------------------------- Version 2.22 -------------------------------- +10/27/09: Update: Change istat to use -B instead of -b (new change in TSK). + +11/19/09: Update: Improved configure script process and error message for +FILE_EXE check. + +11/25/09: Fixed MD5 exe bug when building live CD + +12/30/09: Fixed issue 2923857 re: cookie errors for the icon and css file +links when cookies are used. + +--------------------------- Version 2.21 -------------------------------- +11/7/08: Bug Fix: Changed case management code to not error when 'dls ...' +line was encountered. + +11/14/08: Bug Fix: Fixed bug 2288406 (parsing of new fls -l format when file name searching and deleted file listing) + +--------------------------- Version 2.20 -------------------------------- +7/1/08: Update: Updated FAT sizes based on new "special" files. + +7/9/08: Update: Updated NTFS processing for orphan files / removed +ifind -p etc. + +7/9/08: Update: Updated mactime and time formats to ISO formats. + +9/13/08: Update: Changed usage to new TSK d* to blk* names. + +9/26/08: Bug Fix: Input check on host was printing invalid host values +w/out encoding HTML entities. Reported by Russ McRee. + +10/01/08: Update: HFS support is enabled if TSK was compiled with +support for it. + +10/08/08: Bug Fix: Added some more HTML entity escaping to case management +values (such as description). Reported by Daniel Medianero. + +10/13/08: Update: Added perl version check back into configure, but used +perl $] variable to do checking. Based on patch by Joerg Friedrich. + +--------------------------- Version 2.10 -------------------------------- +2/20/08: Bug Fix: Added 'tsk' to the path for sorter to find the 'images' +config file. Reported by Russell Reynolds. + +3/2/08: Update: Modified the adding of disk image process to save a +call to mmls (reported by Pope). + +3/2/08: Update: Added more basic control char filtering back into Print(). + +--------------------------- Version 2.09 -------------------------------- +2/4/07: Update: Bind only to localhost network if remote addr is local. +Suggested by Markus Waldeck. + +4/19/07: Bug Fix: Event sequencer notes for file did not have clock skew +in the times. Reported by Len CulBreath. + +12/21/07: Update: updated configure and install process for TSK 2.50 + +1/28/08: Update: Added NSRL support back in. + +--------------------------- Version 2.08 -------------------------------- + +8/23/06: Bug Fix: The configure script did not like TSK directory names +with a space in them. + +8/23/06: Update: The PATH variable is not entirely cleared anymore. +Instead, it is replaced by the basic bin directories (this was causing +some problems with Cygwin). + +8/31/06: Update: If Autopsy is running under Cygwin, then it will set +the PATH to contain the basic bin directories. Otherwise, it is clear +(original behavior). + + +--------------------------- Version 2.07 -------------------------------- +3/15/06: Bug Fix: Caseman.pm had DATA_DIR instead of DATADIR and a +concatenation error message occurred. Reported by Jason DePriest. + +5/3/06: Update: Added support for ISO9660 file systems. + +5/3/06: Update: Added support for AFF and AFD image formats. + +5/03/06: Update: Added image format type to image details screen. + +5/3/06: Update: Added hexdump view for file analysis and reports (initial +patch by Patrick Knight). + +5/3/06: Update: Changed number of dashes in reports to 70 instead of 62. + +5/4/06: Update: Integrity checking disabled for non-raw image files +until a specialized tool exists in TSK to abstract the embedded hash +calculation. + +5/8/06: Update: Added support for AFM files. + + + +--------------------------- Version 2.06 -------------------------------- +05/02/05: Fix: Typo in timeline creation window (reported by Surago Jones). + +06/15/05: Update: Added css style sheet and changed some formatting. + +08/13/05: Update: Added "utf-8" as HTML type so that TSK unicode +output will be properly dispayed. + +10/13/05: Update: Removed print_output() function contents because +it broke the Unicode chars. + +10/13/05: Update: Require 5.8 version of Perl now (in config and +in source) because it has best Unicode support. + + + +--------------------------- Version 2.05 -------------------------------- +03/16/05: Update: Image name is given in the Image Details window +when adding a new image file. (Suggested by Surago Jones). + +03/17/05: Bug Fix: swap and raw host config entries could not be +read after the conversion because of a regular expression bug in +the read code. (Reported by Surago Jones) (BUG: 1165235) + +03/21/05: Bug Fix: When a new host was added to a case with no +investigator names, then it would prompt you to select a name from +an empty list. (BUG: 1167970). + +03/25/05: Update: Check return status of rename functions and print +error if failed. + +04/04/05: Bug Fix: A missing volume type message was reported when +adding a disk image. The flow of add_img_prep was modified to +ensure that it was set. (Reported by Bradley Bitzkowski) (BUG: +1177042) + +04/08/05: Update: A thumbnail of images is shown when selected in the File +mode. Suggested by and patch by Guy Voncken. + + +--------------------------- Version 2.04 -------------------------------- +10/22/04: Update: Changed the way that NTFS lists directory contents. No + longer lists the deleted entries from 'fls', only from 'ifind'. Reduces + the inaccurate information. + +02/XX/05: Update: Incorporated new TSK 2 features: + - Disk images (split and raw) + - new config file formats + - moved images and output md5.txt file into one + +03/01/05: Update: Changed behavior of some links that created new +Autopsy Windows + +03/05/05: Update: timeline output can be in comma delimited format + +03/05/05: Update: Added SSN and credit card seach patterns from + Jerry Shenk. + +03/05/05: Update: Added temporal data when a note is creaed. + +03/11/05: Update: Changed to new TSK names for srch_strings and img_stat + +03/15/05: Update: improved handling of white space around investigator +names and image names (suggested by Brian Baskin). + + +--------------------------- Version 2.03 -------------------------------- +08/24/04: Update: Added SHA-1 hash to the metadata view. + +09/01/04: Update: Added sstrings instead of local version of strings. + +09/05/04: Update: Added more help text. + +09/06/04: Update: Use the local version of file if TSK version is +not found. + +09/06/04: Update: Added links to the notes and events page after a +note or event has been created. + +09/06/04: Update: Added Unicode extract and search functionality using +the 'sstrings' tool from TSK. + + +--------------------------- Version 2.02 -------------------------------- +07/19/04: Bug Fix: print_err message in Caseman.lib did not have correct +Print:: package, which caused an error (BUG: 994199). + +07/29/04: Update: Added support for NTFS 'ifind -p' option to find deleted +files that do not have a name in the parent directory. + +07/29/04: Update: Added a filter to remove duplicate entries from a file +listing. Duplicate names with the same name and meta address are +removed. + +07/29/04: Update: OS X no longer needs the strings script, Autopsy +will adjust for the different flags. + +07/29/04: Update: When a deleted file name is entered into the find +directory box, the recover bit is set so the full contents are shown. + + +--------------------------- Version 2.01 -------------------------------- +03/29/04: Update: Changed text for the data integrity option when +adding a new image. + +04/20/04: Bug Fix: Fixed error that occured when data browsing with +a raw or swap image. The TSK usage for these file system types was +inconsistent and it was fixed in version 1.69. (BUG: 925382). +(Reported by Harald Katzer) + +05/03/04: Update: Changed regular expression in META so that the +new recovery listing in FAT istat will not show up as a hyperlink. + +05/03/04: Update: Removed usage of '-H' with 'icat' in File.PM. + +05/20/04: Bug Fix: Fixed the incorrect error message that was printed +when installing autopsy with a newer version of TSK than 1.68. +(BUG: 938909) + +05/20/04: Update: Added new feature that allows perl regular +expressions to be used to find file names. + +05/20/04: Update: Added file recovery features to File.pm, Meta.pm, +and Appview.pm. + +05/27/04: Update: Added a space to $REG_ZONE2 so that CYGWIN would +work if no zone was given (Marcus Muller). + +/05/27/04: Update: Added 'p' as an option for the type of a file in the +'fls' output and made the $::REG_MTYPE global for the pattern. + +05/28/04: Update: Cleaned up code so that commands and directories +do not have double slashes (//) sometimes. This caused problems +with CYGWIN (reported by Marcus Muller). + +05/28/04: Bug Fix: Keyword search of unallocated space would link to +incorrect data unit (although the address was correct). (Reported by +Jorge Ortiz, David Perez, Raul Siles). (BUG: 962410). + +05/28/04: Update: Updated dcat usage and syntax to reflect changes to +TSK. + +05/28/04: Update: Changed the messages printed when multiple data units +were displayed. Now the number of units or range are given instead of +number of bytes. + + +--------------------------- Version 2.00 -------------------------------- +11/25/03: Update: made evidence locker directory names constant (define.pl) +11/25/03: Update: Started process of re-architecture +12/2/03: Update: Replaced logo.jpg with Hash the Hound +12/7/03: Update: Added favicon.ico with Hash +01/06/04: Update: Changed command line arguments +01/24/04: Update: made it only a warning if cookie file can't be opened +02/15/04: Update: Timezone is now optional. Defaults to local if not given. +02/15/04: Update: Timezone value optional in () in file listing (prevents + parsing errors if incorrect timezone is given +03/16/04: Bug Fix: Fixed zombie problem by ignoring child signal +(BUG: 860186) Reported by Angus Marshall. +03/18/04: Update: New layout for adding cases, hosts, and images. +03/18/04: Update: changed HTML to use lowercase values instead of all caps. +03/18/04: Update: New windows are no longer opened when changing modes. +03/19/04: Release: Big release with a new redesign and a few other + changes (live analysis) + +--------------------------- Version 1.75 -------------------------------- +09/22/03: Update: Changed the internal 'get_' functions that parse the + URL arguments to error instead of just return 0 when a problem occurs. +10/22/03: Bug Fix: Check for an investigator name before trying to log + to the exec log. This is a problem when indexing a hash database, an + error message is printed because of the null string. reported by + Brian Baskin. +11/10/03: Update: Improved error message when strings can't be parsed. + (Bug: 823081) +11/15/03: Update: Improved messages in installation script +11/15/03: Bug Fix: Added 'defined' checks to command output to prevent + string errors when command fails. (BUG 842824) +11/15/03: Update: Added 'HEIGHT' value to HTML images to make images + align better and load faster and with the right size +11/15/03: Update: Added a timer so that a char is printed every 5 seconds + during keyword searching, file type sorting, and MD5 for images. + +--------------------------- Version 1.74 -------------------------------- +08/03/03: Bug Fix: Notes could not be added for some files because + the HTML code was missing a closing bracket. +08/18/03: Bug Fix: added POSIX:settz() because some versions of Perl do + not use the most recent ENV{TZ} variable when running 'localtime'. This + cause some incorrect times for events in the sequencer. +08/19/03: Update: NSRL is no longer used with 'sorter' until it is + easier to identify which files in the NSRL are known good and which + are known bad. +08/20/03: Update: Added support for swap and raw images for searching + and data unit analysis. +08/20/03: Update: Added the unit size to the display of the Data Unit + mode. +08/20/03: Update: Search for perl5.6.0 first during install +08/21/03: Update: Changed use of backticks to pipes for executing commands +08/21/03: ?: Added a 'sleep(1)' to the pipe to prevent the loss of data + that can be seen with perl5.8.0 in the buffer. This should be fixed + in a better way though. +08/21/03: Update: The exact command executed is now saved to the log + directory. +08/21/03: Update: Changed 'date' regexp to make year optional. +08/22/03: Update: Added warning if Perl 5.8 is used because of the buffer + problem. +08/22/03: Bug Fix: Fixed some keyword escape values in the search mode. +08/22/03: Update: Added a new help page on the limitations of keyword + searching. +08/22/03: Update: Moved the unallocated space and strings file creation + to the Image Details view instead of the keyword search window + (suggested by: Paul Bakker) +08/25/03: Update: improved wording of the Add Image window to better + explain the mounting point. +08/26/03: Update: When adding sequencer notes in manually, the time + is set to the last note entered to make it easier to add notes from + logs and external sources. +08/26/03: Update: The keyword search display has a final clause that + prints the results even if they are not found in the 'index' method. + This prevents any hits from being lost during the analysis of the + output. +08/26/03: Bug Fix: strings less than 4 chars would not be found before + because 'strings' only shows strings that are 4 or more in length +08/28/03: Update: if more than 1000 keyword hits are found, then a message + is reported and the user must choose a new keyword. This prevents the + browser from hanging from a huge HTML table. +08/28/03: Update: A '.' is printed during the keyword search for each + 100 hits as a status update. + + +--------------------------- Version 1.73 -------------------------------- +06/10/03: Bug Fix: The '-i day' was not added to the mactime code and + caused an error (reported by Cathy Buckman) + +--------------------------- Version 1.72 --------------------------------- +04/09/03: Bug Fix: The Java Script check on the main page broke in 1.71 + because the document.write was on multiple lines +04/11/03: Bug Fix: Keyword Search False Hit code had a bug that it + would be printed in error and message was improved +04/22/03: Update: Added examples to case management help file +05/06/03: Bug Fix: calc_md5 did not need 'o' tag on end of regular + expression because it would not work if the method was called more + than once. (Paul Bakker) +06/01/03: Bug Fix: Some keyword searches with $ in it were failing +06/01/03: Update: Keyword searches are now saved to a file and can be + found in the keyword seach main menu +06/01/03: Update: Changed the format a little of the keyword search + menu +06/01/03: Update: Added grep cheat sheet +06/03/03: Update: Tables now have alternating colors for file listing + and timeline viewing +06/03/03: Update: Sequencer mode added +06/03/03: Update: Sequencer help file added +06/04/03: Bug Fix: Added 'LANG=C LC_ALL=C' to sorter & mactime to prevent + UTF-8 errors (Debugging help from Daniel Schwartzer) +06/04/03: Bug Fix: The regular expression for viewing timelines did not + allow multiple users to have the same UID (reported by Cathy Buckman) +06/05/03: Update: Added button for Event Sequencer and added tables to + the standard notes reading window +06/09/03: Update: Added '-i day' flag to mactime for new feature in + The Sleuth Kit + +--------------------------- Version 1.71 --------------------------------- +02/27/03: Bug Fix: Regular expression searches w/out a strings file had + problems because the '-n' value was being incorrectly calculated. +03/17/03: Update: Added more logging to investigator log +03/17/03: Bug Fix: The case opening was not being logged in the case log +03/17/03: Update: The current 'mode' tab is also a hyperlink now +03/17/03: Bug Fix: Fixed bug that did not allow the path for a strings + file to have a space in it. +03/17/03: Update: When no port and remote address are given on the + command line, port 9999 and localhost are used. Documents also + updated to reflect new syntax. +03/18/03: Update: Use the 'x' repetition operator for ASCII reports + instead of a row of dashes. +03/18/03: Update: Added <NOFRAMES> tag to MAIN_FR and incorporated more + '<<EOF' HTML code. +03/19/03: Update: Added $FIL_NAME function that translates a name to + a meta data address using 'ifind -n' +03/19/03: Update: A directory name can be entered in the $FIL_DIR + frame now to jump to a directory or file +03/19/03: Update: The directory path in $FIL_LIST was changed to have + hyperlinks that allow one to jump to a previous directory (using + $FILE_NAME) +03/19/03: Update: Cleaned up HTML code in $FIL_LIST +03/20/03: Update: passwd and group files are now imported in timelines + by selecting the image - no more inode values +03/20/03: Update: Cleaned up HTML code in timeline section +03/21/03: Update: Added '-z' flag to usage of 'file' so that comressed + files are opened. +03/21/03: Bug Fix: Some special values needed to be escaped in the + grep keyword search (for non regular expressions) (\.]^$"-). +03/24/03: Update: Changed how images are added (symlinks, copies, + or moves). +03/24/03: Update: Added a file system sanity check when adding one +03/27/03: Update: Added a check to the 'File Type' mode that extracts + just graphic images and makes thumbnails. +03/27/03: Update: Added '-i' flag when 'mactime' is run to create the + summary file for timelines. +03/27/03: Update: Added link to summary page with hyper links to actual + month for timelines +03/27/03: Update: Added more HTML table columns for date in timeline view +03/27/03: Update: Made the 'ifind' process optional in Data Unit and key + word searching mode (makes browsing faster) +03/27/03: Update: Evidence Locker now contains entries for when a case + is created or opened. +03/30/03: Update: Improved the help file for time lines. +03/31/03: Update: Changed addresses to sleuthkit.org + + + +--------------------------- Version 1.70 --------------------------------- +Interface Changes: + - Too many to note individually + - New windows are created when modes or images are changed + - Improved error messages + - Can load the unallocated image in the Data Unit Mode + - Case management + +12/10/02: Update: Help is now a directory and contents can be viewed at + any time. +01/02/03: Update: Added support for sorter and hfind tools in TASK +01/02/03: Update: NSRL now requested at startup +01/02/03: Update: Alert and exclude hash databases are options when making + a new host now +01/09/03: Update: Carriage Returns are now sent if it is a Windows client +01/09/03: Update: Improved the pre-defined IP keyword search expression +01/10/03: Update: Changed use of "_new" as target to "_blank" +01/28/03: Update: Installation and other system directories can now + have spaces and other symbols in them (Dave Goldsmith) + + +--------------------------- Version 1.62 --------------------------------- +10/07/02: Update: Added File Type to block mode +10/07/02: Update: Can now add notes to 'dls' image blocks +10/07/02: Update: One can now view as many consecutive data units as they + want in data mode. Many other changes and updates were done with this + as well. (inspired by the Honeynet sotm) +10/07/02: Update: The File System details view for FAT now has hyperlinks + to view the run and follow to the next run. +10/09/02: Bug Fix: Removed use of 'use integer' so that large blocks do + no turn into '-1' when doing a keyword search (Michael Stone - Loyola) + + +--------------------------- Version 1.61 --------------------------------- +08/28/02: Update: White space is allowed at the begining of the morgue file +08/28/02: Bug Fix: No error is generated if md5.txt does not exist from + main menu +08/28/02: Update: Improved error messages +08/28/02: Update: Added code to Main Menu to check for Java Script turned on +09/19/02: Update: fsmorgue can be a symlink in the morgue directory + + +--------------------------- Version 1.60 --------------------------------- +- Changed NTFS c-time to Changed from Created (5/20/02) +- Fixed a couple little bugs with parsing NTFS output (5/20/02) +- Improved sorting (name is case insensitive and name is used as + secondary sorting index) (5/20/02) +- Improved error messages of invalid input to inode & block mode +- Added ability to import password and group files when making a time line + (5/28/02) +- Fixed bug that did not allow IP addresses to be used for the ACL when + DNS was not available (5/30/02) +- Fixed some issues to make Internet Explorer not complain so much (05/30/02) +- Improved the logging so that one can retrace their actions (05/31/02) +- Moved autopsy.log to logs directory (05/31/02) +- Added ability to write Notes about a given block, inode, or file (06/04/02) + (suggestion by Dave Dittrich) +- Set default investigators name (an error was generated if no name was given) + (06/04/02) +- Added links in the help page to the window help pages (06/05/02) +- Updated timeline to reflect new format in new TASK (06/19/02) +- Added '-C' flag to turn off cookies on command line (06/20/02) +- Added new main menu (06/20/02) +- Made MD5 generation 'opt-out' (06/22/02) +- New code to remove duplicate entries in md5.txt and fsmorgue +- fsmorgue can have whitespace at end of line (7/6/02) +- An error is generated if an image in fsmorgue does not exist (7/6/02) +- updated automatic date search (7/9/02) +- New feature allows one to save the MD5 values of all files in a directory, + which makes the Solaris Finger Print Database easier (7/12) + + +--------------------------- Version 1.50 --------------------------------- +- Modified to support TASK instead of TCT and TCTUTILs (8/25/01) +- Removed chmod 'bug' for the cookie file (8/25/01) +- Fixed number of hits bug in Search mode (off by one) (8/25/01) +- Added ftype support (8/28/01) +- Added ftype field to reports (8/28/01) +- Encoded dir arg in FIL_DEL +- Filter option holds for usage of next and rev in block mode +- If using fat, a separate option is given to run find_inode due to how + slow it runs +- removed use of zoneinfo in favor of the new timezone value in fsmorgue. +- strings now uses '-a' flag to show all strings +- When doing a search, the length of the string is given as the '-n' + flag to strings to speed up the search +- Allow user to "force" blocks when an inode size is 0 (the istat -b flag) +- use the md5 that comes with TCT/TASK +- multiple images with the same mounting point can now exist +- Added the morgue directory to the MENU to make it easier to manage + multiple hosts +- Files are sorted by name by default +- can import strings files and create them if needed +- Run files through 'file' to get data type +- case insensitive searches +- MAC headers correspond to file system type (create vs change) +- Deleted files are displayed in red +- Correct address name used (fragment, sector etc.) +- Support for NTFS attributes +- parse bad tags from HTML when viewing it (send sterile pict) +- cookie file has port number to aid in scripting +- cookie files are deleted upon closing +- log messages are printed for each request +- added integrity checker +- renamed aux directory to base to make Windows happy +- added time line support +- added fsstat support +- Added built-in search values in search.pl + + +May 29, 2001 1.01 released +- Fixed Hex link when in search mode (3/23/01) +- Corrected heading of ctime (Addam Schroll, Purdue University) (4/24/01) +- Parses output of new istat correctly (5/1/01) +- When viewing 'inode as a file', the image and inode are sent as the dir + name (5/1/01) +- Added wait() to collect zombies in Linux (5/22/01) +- Added auto-flush to prevent repeat log entries (5/22/01) +- Added a 'save as' option to file and inode browsing (Addam Schroll) + (5/22/01) +- Added option for unrm block numbers (due to blockcalc) (5/22/01) +- Improved side menu for inode, block, and search (5/22/01) +- Added "Content-Disposition" so that reports and "save as" have a + unique default filename. (5/23/01) +- Organization changes to Main Menu (5/24/01) +- Automated installation process (5/24/01) + +March 19, 2001 1.0 released +- Added man page for autopsy (3/10/01) +- Directory entries in config files no longer require an / at the end +- Morgue file names can have a '.' in them (but still not '/') (3/10) +- autopsy first checks for /dev/urandom for random cookie (3/10/01) +- morgue directory is a command line option to autopsy (3/10/01) +- the lib variable in autopsy is no longer set to './' so that it + can be run outside of /usr/local/autopsy (3/10/01) +- changed all references of device to image (3/11/01) +- changed all reports to print full image path (3/11/01) +- Investigator is a command line option to autopsy (3/11/01) +- CGI support removed. Only autopsy is supported (3/16/01) +- renamed autopsyd to autopsy (3/16/01) +- Fixed UID and GID heading (3/16/01) +- Run image through strings before grep to prevent memory errors (3/16/01) +- output of find_file and find_inode is prepended with rdir (3/16/01) + + +Feb 27, 2001 0.2b released +- Added stand alone server, autopsyd (as suggested by Dan Farmer) +- Reorganized files due to new program +- Changed names of some executables that changed in TCTUTILs + +Feb 19, 2001 0.1b released + +------------------------------------------------------------------------ diff --git a/build.xml b/build.xml index f1f6586b11..dbd3c005ca 100644 --- a/build.xml +++ b/build.xml @@ -96,7 +96,9 @@ <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 --> + 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" /> From 97ce900212e032112540b7e9a5c6935c460722d4 Mon Sep 17 00:00:00 2001 From: Brian Carrier <carrier@sleuthkit.org> Date: Wed, 4 Sep 2013 17:43:06 -0400 Subject: [PATCH 20/21] Added quick fix to prevent crash when playing videos from recent merge --- .../org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java index 2914bc5282..1eea6fb84d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java @@ -514,6 +514,10 @@ public class GstVideoPanel extends MediaViewVideoPanel { 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) { From 6a802f4dac70746581038af1f064a5ad58c389b0 Mon Sep 17 00:00:00 2001 From: Brian Carrier <carrier@sleuthkit.org> Date: Wed, 4 Sep 2013 17:59:23 -0400 Subject: [PATCH 21/21] Fixed video playback issue. --- .../corecomponents/DataContentViewerMedia.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index a9c83e5812..af1d14d312 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -126,12 +126,10 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo if (file.equals(lastFile)) { return; //prevent from loading twice if setNode() called mult. times - } else { - lastFile = file; - } - - videoPanel.reset(); - + } + + resetComponent(); + final Dimension dims = DataContentViewerMedia.this.getSize(); if (imagePanelInited && containsExt(file.getName(), IMAGES)) { @@ -142,6 +140,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo videoPanel.setupVideo(file, dims); switchPanels(true); } + lastFile = file; } catch (Exception e) { logger.log(Level.SEVERE, "Exception while setting node", e); } @@ -183,7 +182,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public void resetComponent() { - videoPanel.reset(); + videoPanel.reset(); + // @@@ Seems like we should also reset the image viewer... + lastFile = null; } @Override