From c8ac16f7135bc3d0b85454c6af6bff6d330bf8b2 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 17 Jul 2015 12:50:53 -0400 Subject: [PATCH] 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 --- .../autopsy/corecomponents/FXVideoPanel.java | 18 +- .../autopsy/corecomponents/GstVideoPanel.java | 288 ++++++++---------- .../autopsy/coreutils/ImageUtils.java | 83 +---- .../autopsy/coreutils/VideoUtils.java | 114 +++++++ 4 files changed, 244 insertions(+), 259 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java index 1410bc50c9..7cf07f7c89 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -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 diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java index db5f97b40f..a32468bf90 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit 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 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 badVideoFiles = Collections.synchronizedSet(new HashSet()); - + /** * 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 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( @@ -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)) ); }// @@ -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 { - 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 { + 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; + 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 diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 89c4a94d9a..19416aae15 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -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() { + } } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java new file mode 100644 index 0000000000..e0d79bce00 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java @@ -0,0 +1,114 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.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() { + } +}