cleanup GstVideoPanel to bring into closer alignment with FXVideoPanel in preparation for further abstraction

check if file exists before exporting in GstVideoPanel
move getTempVideoFile and generateVideoThumbnail to new VideoUtils class
This commit is contained in:
jmillman 2015-07-17 12:50:53 -04:00
parent eae987ba5b
commit c8ac16f713
4 changed files with 244 additions and 259 deletions

View File

@ -63,6 +63,7 @@ import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
@ -142,11 +143,10 @@ public class FXVideoPanel extends MediaViewVideoPanel {
}
mediaPane.setInfoLabelText(path);
mediaPane.setInfoLabelToolTipText(path);
final File tempFile = getTempFile(currentFile);
final File tempFile = VideoUtils.getTempVideoFile(currentFile);
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
ExtractMedia em = new ExtractMedia(currentFile, tempFile);
em.execute();
new ExtractMedia(currentFile, tempFile).execute();
}
mediaPane.setFit(dims);
@ -162,18 +162,6 @@ public class FXVideoPanel extends MediaViewVideoPanel {
currentFile = null;
}
/**
* a file in the Case's temp folder to write the video content to for
* playback.
*
* @param file
*
* @return
*/
private java.io.File getTempFile(AbstractFile file) {
return Paths.get(Case.getCurrentCase().getTempDirectory(), "videos", file.getId() + "." + file.getNameExtension()).toFile();
}
/**
* 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

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.IntBuffer;
import java.util.ArrayList;
@ -41,7 +42,6 @@ import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.gstreamer.ClockTime;
import org.gstreamer.Gst;
import org.gstreamer.GstException;
@ -52,26 +52,25 @@ import org.gstreamer.elements.RGBDataSink;
import org.gstreamer.swing.VideoComponent;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ServiceProviders(value = {
@ServiceProvider(service = FrameCapture.class)
})
public class GstVideoPanel extends MediaViewVideoPanel {
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
private static final List<String> MIMETYPES = Arrays.asList("video/quicktime", "audio/mpeg", "audio/x-mpeg", "video/mpeg", "video/x-mpeg", "audio/mpeg3", "audio/x-mpeg-3", "video/x-flv", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
private static final Logger logger = Logger.getLogger(GstVideoPanel.class.getName());
private boolean gstInited;
private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
@ -87,7 +86,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
private AbstractFile currentFile;
private final Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
/**
* Creates new form MediaViewVideoPanel
*/
@ -129,30 +128,27 @@ public class GstVideoPanel extends MediaViewVideoPanel {
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
progressSlider.setValue(0);
progressSlider.addChangeListener(new ChangeListener() {
progressSlider.addChangeListener((ChangeEvent e) -> {
/**
* Should always try to synchronize any call to
* progressSlider.setValue() to avoid a different thread
* changing playbin while stateChanged() is processing
*/
@Override
public void stateChanged(ChangeEvent e) {
int time = progressSlider.getValue();
synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) {
State orig = gstPlaybin2.getState();
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.setState(orig);
int time = progressSlider.getValue();
synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) {
State orig = gstPlaybin2.getState();
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.setState(orig);
}
}
});
@ -195,7 +191,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
progressSlider.setEnabled(false);
return;
}
String path = "";
try {
path = file.getUniquePath();
@ -207,7 +203,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
pauseButton.setEnabled(true);
progressSlider.setEnabled(true);
java.io.File ioFile = getJFile(file);
java.io.File ioFile = VideoUtils.getTempVideoFile(file);
gstVideoComponent = new VideoComponent();
synchronized (playbinLock) {
@ -219,14 +215,13 @@ public class GstVideoPanel extends MediaViewVideoPanel {
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(gstVideoComponent);
videoPanel.setVisible(true);
gstPlaybin2.setInputFile(ioFile);
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
@ -239,11 +234,8 @@ public class GstVideoPanel extends MediaViewVideoPanel {
void reset() {
// reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressLabel.setText("");
}
SwingUtilities.invokeLater(() -> {
progressLabel.setText("");
});
if (!isInited()) {
@ -281,41 +273,27 @@ public class GstVideoPanel extends MediaViewVideoPanel {
currentFile = null;
}
private java.io.File getJFile(AbstractFile file) {
// Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory();
String name = file.getName();
int extStart = name.lastIndexOf(".");
String ext = "";
if (extStart != -1) {
ext = name.substring(extStart, name.length()).toLowerCase();
}
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
java.io.File tempFile = new java.io.File(tempPath);
return tempFile;
}
/**
* @param file a video file from which to capture frames
* @param file a video file from which to capture frames
* @param numFrames the number of frames to capture. These frames will be
* captured at successive intervals given by durationOfVideo/numFrames. If
* this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one
* frame will be captured and returned.
* captured at successive intervals given by durationOfVideo/numFrames. If
* this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one
* frame will be captured and returned.
*
* @return a List of VideoFrames representing the captured frames.
*/
@Override
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
List<VideoFrame> frames = new ArrayList<>();
Object lock = new Object();
FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock);
if (!isInited()) {
return frames;
}
// throw exception if this file is known to be problematic
if (badVideoFiles.contains(file.getName())) {
throw new Exception(
@ -374,7 +352,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
if (!playbin.seek(timeStamp, unit)) {
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); //NON-NLS
}
ret = playbin.play();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
@ -384,7 +362,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
}
// wait for FrameCaptureRGBListener to finish
synchronized(lock) {
synchronized (lock) {
try {
lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
} catch (InterruptedException e) {
@ -400,7 +378,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
throw new Exception(
NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemStopCaptFrame.msg"));
}
if (image == null) {
logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName()); //NON-NLS
badVideoFiles.add(file.getName());
@ -412,13 +390,13 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return frames;
}
private class FrameCaptureRGBListener implements RGBDataSink.Listener {
public FrameCaptureRGBListener(Object waiter) {
this.waiter = waiter;
}
private BufferedImage bi;
private final Object waiter;
@ -438,7 +416,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return image;
}
}
}
/**
@ -460,12 +438,12 @@ public class GstVideoPanel extends MediaViewVideoPanel {
javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
videoPanel.setLayout(videoPanelLayout);
videoPanelLayout.setHorizontalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
videoPanelLayout.setVerticalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 231, Short.MAX_VALUE)
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 231, Short.MAX_VALUE)
);
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(GstVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
@ -482,48 +460,48 @@ public class GstVideoPanel extends MediaViewVideoPanel {
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
controlPanel.setLayout(controlPanelLayout);
controlPanelLayout.setHorizontalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(infoLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())))
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(infoLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())))
);
controlPanelLayout.setVerticalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pauseButton)
.addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(infoLabel)
.addContainerGap())
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pauseButton)
.addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(infoLabel)
.addContainerGap())
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
);
}// </editor-fold>
@ -557,9 +535,10 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return;
}
} else if (state.equals(State.READY)) {
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute();
em.getExtractedBytes();
final File tempVideoFile = VideoUtils.getTempVideoFile(currentFile);
if (tempVideoFile.exists() == false || tempVideoFile.length() < currentFile.getSize()) {
new ExtractMedia(currentFile, tempVideoFile).execute();
}
}
}
}//GEN-LAST:event_pauseButtonActionPerformed
@ -572,14 +551,13 @@ public class GstVideoPanel extends MediaViewVideoPanel {
private javax.swing.JSlider progressSlider;
private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables
private class VideoProgressWorker extends SwingWorker<Object, Object> {
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
private long millisElapsed = 0;
private final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50;
private boolean hadError = false;
private boolean isPlayBinReady() {
synchronized (playbinLock) {
@ -612,9 +590,9 @@ public class GstVideoPanel extends MediaViewVideoPanel {
/**
* @return true while millisElapsed is greater than END_TIME_MARGIN_MS
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
*/
private boolean hasNotEnded() {
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
@ -626,8 +604,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
// enable the slider
progressSlider.setEnabled(true);
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
ClockTime pos = null;
ClockTime pos;
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
synchronized (playbinLock) {
@ -637,11 +614,11 @@ public class GstVideoPanel extends MediaViewVideoPanel {
// pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000;
elapsedHours = (int) secondsElapsed / 3600;
int elapsedHours = (int) secondsElapsed / 3600;
secondsElapsed -= elapsedHours * 3600;
elapsedMinutes = (int) secondsElapsed / 60;
int elapsedMinutes = (int) secondsElapsed / 60;
secondsElapsed -= elapsedMinutes * 60;
elapsedSeconds = (int) secondsElapsed;
int elapsedSeconds = (int) secondsElapsed;
String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds,
@ -666,8 +643,7 @@ public class GstVideoPanel extends MediaViewVideoPanel {
return null;
}
@Override
protected void done() {
// see if any exceptions were thrown
@ -676,54 +652,38 @@ public class GstVideoPanel extends MediaViewVideoPanel {
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.WARNING, "Error updating video progress: " + ex.getMessage()); //NON-NLS
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.infoLabel.updateErr",
ex.getMessage()));
ex.getMessage()));
} // catch and ignore if we were cancelled
catch (java.util.concurrent.CancellationException ex) {
}
// catch and ignore if we were cancelled
catch (java.util.concurrent.CancellationException ex ) { }
}
} //end class progress worker
/* Thread that extracts and plays a file */
private class ExtractMedia extends SwingWorker<Object, Void> {
private class ExtractMedia extends SwingWorker<Long, Void> {
private ProgressHandle progress;
boolean success = false;
private AbstractFile sFile;
private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes;
private final AbstractFile sourceFile;
private final java.io.File tempFile;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
this.sFile = sFile;
this.jFile = jFile;
}
public long getExtractedBytes() {
return extractedBytes;
ExtractMedia(AbstractFile sFile, java.io.File jFile) {
this.sourceFile = sFile;
this.tempFile = jFile;
}
@Override
protected Object doInBackground() throws Exception {
success = false;
progress = ProgressHandleFactory.createHandle(
NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sFile.getName()),
new Cancellable() {
@Override
public boolean cancel() {
return ExtractMedia.this.cancel(true);
}
});
protected Long doInBackground() throws Exception {
progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> ExtractMedia.this.cancel(true));
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
progress.start();
progress.switchToDeterminate(100);
progress.start(100);
try {
extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true);
return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
return 0L;
}
success = true;
return null;
}
/* clean up or start the worker threads */
@ -746,11 +706,11 @@ public class GstVideoPanel extends MediaViewVideoPanel {
}
void playMedia() {
if (jFile == null || !jFile.exists()) {
if (tempFile == null || !tempFile.exists()) {
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
return;
}
ClockTime dur = null;
ClockTime dur;
synchronized (playbinLock) {
// must play, then pause and get state to get duration.
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
@ -766,7 +726,6 @@ public class GstVideoPanel extends MediaViewVideoPanel {
gstPlaybin2.getState();
dur = gstPlaybin2.queryDuration();
}
duration = dur.toString();
durationMillis = dur.toMillis();
// pick out the total hours, minutes, seconds
@ -777,29 +736,26 @@ public class GstVideoPanel extends MediaViewVideoPanel {
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0);
SwingUtilities.invokeLater(() -> {
progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0);
synchronized (playbinLock) {
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
synchronized (playbinLock) {
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
}
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
});
}
}
@Override
public String[] getExtensions() {
return EXTENSIONS;
return EXTENSIONS.clone();
}
@Override

View File

@ -37,8 +37,6 @@ import javax.imageio.ImageIO;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.highgui.VideoCapture;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
@ -314,80 +312,6 @@ public class ImageUtils {
&& ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
&& ((fileHeaderBuffer[7] & 0xff) == 0x0A));
}
private final static int THUMB_COLUMNS = 3;
private final static int THUMB_ROWS = 3;
private static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) {
final String extension = file.getNameExtension();
java.io.File tempFile = Paths.get(Case.getCurrentCase().getTempDirectory(), "videos", file.getId() + "." + extension).toFile();
try {
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
copyFileUsingStream(file, tempFile);
}
} catch (IOException ex) {
return null;
}
VideoCapture videoFile = new VideoCapture(); // will contain the video
if (!videoFile.open(tempFile.toString())) {
return null;
}
double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second
double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames
if (fps <= 0 || totalFrames <= 0) {
return null;
}
double milliseconds = 1000 * (totalFrames / fps); //total milliseconds
double timestamp = Math.min(milliseconds, 500); //default time to check for is 500ms, unless the files is extremely small
int framkeskip = Double.valueOf(Math.floor((milliseconds - timestamp) / (THUMB_COLUMNS * THUMB_ROWS))).intValue();
Mat imageMatrix = new Mat();
BufferedImage bufferedImage = null;
for (int x = 0; x < THUMB_COLUMNS; x++) {
for (int y = 0; y < THUMB_ROWS; y++) {
if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) {
break;
}
//read the frame into the image/matrix
if (!videoFile.read(imageMatrix)) {
break; //if the image for some reason is bad, return default icon
}
if (bufferedImage == null) {
bufferedImage = new BufferedImage(imageMatrix.cols() * THUMB_COLUMNS, imageMatrix.rows() * THUMB_ROWS, BufferedImage.TYPE_3BYTE_BGR);
}
byte[] data = new byte[imageMatrix.rows() * imageMatrix.cols() * (int) (imageMatrix.elemSize())];
imageMatrix.get(0, 0, data); //copy the image to data
//todo: this looks like we are swapping the first and third channels. so we can use BufferedImage.TYPE_3BYTE_BGR
if (imageMatrix.channels() == 3) {
for (int k = 0; k < data.length; k += 3) {
byte temp = data[k];
data[k] = data[k + 2];
data[k + 2] = temp;
}
}
bufferedImage.getRaster().setDataElements(imageMatrix.cols() * x, imageMatrix.rows() * y, imageMatrix.cols(), imageMatrix.rows(), data);
}
}
videoFile.release(); // close the file
return ScalrWrapper.resizeFast(bufferedImage, iconSize);
}
private static final int CV_CAP_PROP_POS_MSEC = 0;
private static final int CV_CAP_PROP_FRAME_COUNT = 7;
private static final int CV_CAP_PROP_FPS = 5;
/**
* Generate an icon and save it to specified location.
@ -405,7 +329,7 @@ public class ImageUtils {
try {
if (SUPP_VIDEO_EXTENSIONS.contains(extension)) {
if (openCVLoaded) {
icon = generateVideoThumbnail((AbstractFile) content, iconSize);
icon = VideoUtils.generateVideoThumbnail((AbstractFile) content, iconSize);
} else {
return DEFAULT_ICON;
}
@ -444,7 +368,7 @@ public class ImageUtils {
return null;
}
return ScalrWrapper.resizeFast(bi, iconSize);
} catch (IllegalArgumentException e) {
// if resizing does not work due to extremely small height/width ratio,
// crop the image instead.
@ -488,4 +412,7 @@ public class ImageUtils {
}
progress.finish();
}
private ImageUtils() {
}
}

View File

@ -0,0 +1,114 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.coreutils;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import org.opencv.core.Mat;
import org.opencv.highgui.VideoCapture;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import static org.sleuthkit.autopsy.coreutils.ImageUtils.copyFileUsingStream;
import org.sleuthkit.datamodel.AbstractFile;
/**
*
*/
public class VideoUtils {
private final static int THUMB_COLUMNS = 3;
private final static int THUMB_ROWS = 3;
private static final int CV_CAP_PROP_POS_MSEC = 0;
private static final int CV_CAP_PROP_FRAME_COUNT = 7;
private static final int CV_CAP_PROP_FPS = 5;
public static File getTempVideoFile(AbstractFile file) {
return Paths.get(Case.getCurrentCase().getTempDirectory(), "videos", file.getId() + "." + file.getNameExtension()).toFile();
}
static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) {
java.io.File tempFile = getTempVideoFile(file);
try {
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
copyFileUsingStream(file, tempFile);
}
} catch (IOException ex) {
return null;
}
VideoCapture videoFile = new VideoCapture(); // will contain the video
if (!videoFile.open(tempFile.toString())) {
return null;
}
double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second
double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames
if (fps <= 0 || totalFrames <= 0) {
return null;
}
double milliseconds = 1000 * (totalFrames / fps); //total milliseconds
double timestamp = Math.min(milliseconds, 500); //default time to check for is 500ms, unless the files is extremely small
int framkeskip = Double.valueOf(Math.floor((milliseconds - timestamp) / (THUMB_COLUMNS * THUMB_ROWS))).intValue();
Mat imageMatrix = new Mat();
BufferedImage bufferedImage = null;
for (int x = 0; x < THUMB_COLUMNS; x++) {
for (int y = 0; y < THUMB_ROWS; y++) {
if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) {
break;
}
//read the frame into the image/matrix
if (!videoFile.read(imageMatrix)) {
break; //if the image for some reason is bad, return default icon
}
if (bufferedImage == null) {
bufferedImage = new BufferedImage(imageMatrix.cols() * THUMB_COLUMNS, imageMatrix.rows() * THUMB_ROWS, BufferedImage.TYPE_3BYTE_BGR);
}
byte[] data = new byte[imageMatrix.rows() * imageMatrix.cols() * (int) (imageMatrix.elemSize())];
imageMatrix.get(0, 0, data); //copy the image to data
//todo: this looks like we are swapping the first and third channels. so we can use BufferedImage.TYPE_3BYTE_BGR
if (imageMatrix.channels() == 3) {
for (int k = 0; k < data.length; k += 3) {
byte temp = data[k];
data[k] = data[k + 2];
data[k + 2] = temp;
}
}
bufferedImage.getRaster().setDataElements(imageMatrix.cols() * x, imageMatrix.rows() * y, imageMatrix.cols(), imageMatrix.rows(), data);
}
}
videoFile.release(); // close the file
return ScalrWrapper.resizeFast(bufferedImage, iconSize);
}
private VideoUtils() {
}
}