19 package org.sleuthkit.autopsy.corecomponents;
21 import java.awt.Dimension;
22 import java.awt.Image;
23 import java.awt.image.BufferedImage;
24 import java.io.IOException;
25 import java.nio.IntBuffer;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.List;
32 import java.util.concurrent.CancellationException;
33 import java.util.concurrent.ExecutionException;
34 import java.util.concurrent.TimeUnit;
35 import java.util.logging.Level;
36 import javax.swing.BoxLayout;
37 import javax.swing.JButton;
38 import javax.swing.JLabel;
39 import javax.swing.JPanel;
40 import javax.swing.JSlider;
41 import javax.swing.SwingUtilities;
42 import javax.swing.SwingWorker;
43 import javax.swing.event.ChangeEvent;
44 import javax.swing.event.ChangeListener;
45 import org.gstreamer.ClockTime;
46 import org.gstreamer.Gst;
47 import org.gstreamer.GstException;
48 import org.gstreamer.State;
49 import org.gstreamer.StateChangeReturn;
50 import org.gstreamer.elements.PlayBin2;
51 import org.gstreamer.elements.RGBDataSink;
52 import org.gstreamer.swing.VideoComponent;
53 import org.netbeans.api.progress.ProgressHandle;
54 import org.netbeans.api.progress.ProgressHandleFactory;
55 import org.openide.util.Cancellable;
56 import org.openide.util.NbBundle;
57 import org.openide.util.lookup.ServiceProvider;
58 import org.openide.util.lookup.ServiceProviders;
68 @ServiceProviders(value = {
69 @ServiceProvider(service = FrameCapture.class)
72 private static final String[] EXTENSIONS =
new String[]{
".mov",
".m4v",
".flv",
".mp4",
".3gp",
".avi",
".mpg",
".mpeg",
".wmv"};
73 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");
77 private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
78 private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000;
79 private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(
GstVideoPanel.class,
"GstVideoPanel.cannotProcFile.err");
81 private long durationMillis = 0;
86 private boolean autoTracking =
false;
87 private final Object playbinLock =
new Object();
89 private final Set<String> badVideoFiles = Collections.synchronizedSet(
new HashSet<String>());
96 customizeComponents();
104 return progressLabel;
108 return progressSlider;
116 return gstVideoComponent;
129 progressSlider.setEnabled(
false);
130 progressSlider.setValue(0);
132 progressSlider.addChangeListener(
new ChangeListener() {
139 public void stateChanged(ChangeEvent e) {
140 int time = progressSlider.getValue();
141 synchronized (playbinLock) {
142 if (gstPlaybin2 != null && !autoTracking) {
143 State orig = gstPlaybin2.getState();
144 if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
145 logger.log(Level.WARNING,
"Attempt to call PlayBin2.pause() failed.");
146 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
149 if (gstPlaybin2.seek(ClockTime.fromMillis(time)) ==
false) {
150 logger.log(Level.WARNING,
"Attempt to call PlayBin2.seek() failed.");
151 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
154 gstPlaybin2.setState(orig);
163 logger.log(Level.INFO,
"Initializing gstreamer for video/audio viewing");
166 }
catch (GstException e) {
168 logger.log(Level.SEVERE,
"Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e);
170 NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.initGst.gstException.msg"),
173 }
catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
175 logger.log(Level.SEVERE,
"Error initializing gstreamer for audio/video viewing and extraction capabilities", e);
177 NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.initGst.otherException.msg"),
186 void setupVideo(
final AbstractFile file,
final Dimension dims) {
188 infoLabel.setText(
"");
192 infoLabel.setText(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.setupVideo.infoLabel.text"));
193 videoPanel.removeAll();
194 pauseButton.setEnabled(
false);
195 progressSlider.setEnabled(
false);
202 }
catch (TskCoreException ex) {
203 logger.log(Level.SEVERE,
"Cannot get unique path of video file");
205 infoLabel.setText(path);
206 infoLabel.setToolTipText(path);
207 pauseButton.setEnabled(
true);
208 progressSlider.setEnabled(
true);
210 java.io.File ioFile = getJFile(file);
212 gstVideoComponent =
new VideoComponent();
213 synchronized (playbinLock) {
214 if (gstPlaybin2 != null) {
215 gstPlaybin2.dispose();
217 gstPlaybin2 =
new PlayBin2(
"VideoPlayer");
218 gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
220 videoPanel.removeAll();
223 videoPanel.setLayout(
new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
224 videoPanel.add(gstVideoComponent);
226 videoPanel.setVisible(
true);
228 gstPlaybin2.setInputFile(ioFile);
230 if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
231 logger.log(Level.WARNING,
"Attempt to call PlayBin2.setState(State.READY) failed.");
232 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
242 SwingUtilities.invokeLater(
new Runnable() {
245 progressLabel.setText(
"");
253 synchronized (playbinLock) {
254 if (gstPlaybin2 != null) {
255 if (gstPlaybin2.isPlaying()) {
256 if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
257 logger.log(Level.WARNING,
"Attempt to call PlayBin2.stop() failed.");
258 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
262 if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) {
263 logger.log(Level.WARNING,
"Attempt to call PlayBin2.setState(State.NULL) failed.");
264 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
267 if (gstPlaybin2.getState().equals(State.NULL)) {
268 gstPlaybin2.dispose();
272 gstVideoComponent = null;
276 if (videoProgressWorker != null) {
277 videoProgressWorker.cancel(
true);
278 videoProgressWorker = null;
288 int extStart = name.lastIndexOf(
".");
290 if (extStart != -1) {
291 ext = name.substring(extStart, name.length()).toLowerCase();
293 tempPath = tempPath + java.io.File.separator + file.
getId() + ext;
295 java.io.File tempFile =
new java.io.File(tempPath);
308 public List<VideoFrame>
captureFrames(java.io.File file,
int numFrames)
throws Exception {
310 List<VideoFrame> frames =
new ArrayList<>();
312 Object lock =
new Object();
320 if (badVideoFiles.contains(file.
getName())) {
322 NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.exception.problemFile.msg", file.
getName()));
326 RGBDataSink videoSink =
new RGBDataSink(
"rgb", rgbListener);
327 PlayBin2 playbin =
new PlayBin2(
"VideoFrameCapture");
328 playbin.setInputFile(file);
329 playbin.setVideoSink(videoSink);
332 StateChangeReturn ret = playbin.play();
333 if (ret == StateChangeReturn.FAILURE) {
335 badVideoFiles.add(file.
getName());
336 throw new Exception(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.exception.problemPlay.msg"));
338 ret = playbin.pause();
339 if (ret == StateChangeReturn.FAILURE) {
341 badVideoFiles.add(file.
getName());
342 throw new Exception(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.exception.problemPause.msg"));
347 TimeUnit unit = TimeUnit.MILLISECONDS;
348 long myDurationMillis = playbin.queryDuration(unit);
349 if (myDurationMillis <= 0) {
354 int numFramesToGet = numFrames;
355 long frameInterval = myDurationMillis / numFrames;
356 if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
361 for (
int i = 0; i < numFramesToGet; ++i) {
362 long timeStamp = i * frameInterval;
364 ret = playbin.pause();
365 if (ret == StateChangeReturn.FAILURE) {
367 badVideoFiles.add(file.
getName());
369 NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.exception.problemPauseCaptFrame.msg"));
374 if (!playbin.seek(timeStamp, unit)) {
375 logger.log(Level.INFO,
"There was a problem seeking to " + timeStamp +
" " + unit.name().toLowerCase());
378 ret = playbin.play();
379 if (ret == StateChangeReturn.FAILURE) {
381 badVideoFiles.add(file.
getName());
383 NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.exception.problemPlayCaptFrame.msg"));
389 lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
390 }
catch (InterruptedException e) {
391 logger.log(Level.INFO,
"InterruptedException occurred while waiting for frame capture.", e);
394 Image image = rgbListener.
getImage();
396 ret = playbin.stop();
397 if (ret == StateChangeReturn.FAILURE) {
399 badVideoFiles.add(file.
getName());
401 NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.exception.problemStopCaptFrame.msg"));
405 logger.log(Level.WARNING,
"There was a problem while trying to capture a frame from file " + file.
getName());
406 badVideoFiles.add(file.
getName());
419 this.waiter = waiter;
422 private BufferedImage
bi;
426 public void rgbFrame(
boolean bln,
int w,
int h, IntBuffer rgbPixels) {
427 synchronized (waiter) {
428 bi =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
429 bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
435 synchronized (waiter) {
449 @SuppressWarnings(
"unchecked")
451 private
void initComponents() {
453 videoPanel =
new javax.swing.JPanel();
454 controlPanel =
new javax.swing.JPanel();
455 pauseButton =
new javax.swing.JButton();
456 progressSlider =
new javax.swing.JSlider();
457 progressLabel =
new javax.swing.JLabel();
458 infoLabel =
new javax.swing.JLabel();
460 javax.swing.GroupLayout videoPanelLayout =
new javax.swing.GroupLayout(videoPanel);
461 videoPanel.setLayout(videoPanelLayout);
462 videoPanelLayout.setHorizontalGroup(
463 videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
464 .addGap(0, 0, Short.MAX_VALUE)
466 videoPanelLayout.setVerticalGroup(
467 videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
468 .addGap(0, 231, Short.MAX_VALUE)
471 org.openide.awt.Mnemonics.setLocalizedText(pauseButton,
org.openide.util.NbBundle.getMessage(
GstVideoPanel.class,
"MediaViewVideoPanel.pauseButton.text"));
472 pauseButton.addActionListener(
new java.awt.event.ActionListener() {
473 public void actionPerformed(java.awt.event.ActionEvent evt) {
474 pauseButtonActionPerformed(evt);
478 org.openide.awt.Mnemonics.setLocalizedText(progressLabel,
org.openide.util.NbBundle.getMessage(
GstVideoPanel.class,
"MediaViewVideoPanel.progressLabel.text"));
480 org.openide.awt.Mnemonics.setLocalizedText(infoLabel,
org.openide.util.NbBundle.getMessage(
GstVideoPanel.class,
"MediaViewVideoPanel.infoLabel.text"));
482 javax.swing.GroupLayout controlPanelLayout =
new javax.swing.GroupLayout(controlPanel);
483 controlPanel.setLayout(controlPanelLayout);
484 controlPanelLayout.setHorizontalGroup(
485 controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
486 .addGroup(controlPanelLayout.createSequentialGroup()
488 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
489 .addGroup(controlPanelLayout.createSequentialGroup()
491 .addComponent(infoLabel)
492 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
493 .addGroup(controlPanelLayout.createSequentialGroup()
494 .addComponent(pauseButton)
495 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
496 .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
497 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
498 .addComponent(progressLabel)
499 .addContainerGap())))
501 controlPanelLayout.setVerticalGroup(
502 controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
503 .addGroup(controlPanelLayout.createSequentialGroup()
505 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
506 .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
507 .addComponent(pauseButton)
508 .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
509 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
510 .addComponent(infoLabel)
514 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
515 this.setLayout(layout);
516 layout.setHorizontalGroup(
517 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
518 .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
519 .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
521 layout.setVerticalGroup(
522 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
523 .addGroup(layout.createSequentialGroup()
524 .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
525 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
526 .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
531 synchronized (playbinLock) {
532 State state = gstPlaybin2.getState();
533 if (state.equals(State.PLAYING)) {
534 if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
535 logger.log(Level.WARNING,
"Attempt to call PlayBin2.pause() failed.");
536 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
539 pauseButton.setText(
"â–º");
541 if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) {
542 logger.log(Level.WARNING,
"Attempt to call PlayBin2.setState(State.PAUSED) failed.");
543 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
546 }
else if (state.equals(State.PAUSED)) {
547 if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
548 logger.log(Level.WARNING,
"Attempt to call PlayBin2.play() failed.");
549 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
552 pauseButton.setText(
"||");
554 if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) {
555 logger.log(Level.WARNING,
"Attempt to call PlayBin2.setState(State.PLAYING) failed.");
556 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
559 }
else if (state.equals(State.READY)) {
578 private String durationFormat =
"%02d:%02d:%02d/%02d:%02d:%02d ";
579 private long millisElapsed = 0;
580 private final long INTER_FRAME_PERIOD_MS = 20;
581 private final long END_TIME_MARGIN_MS = 50;
582 private boolean hadError =
false;
585 synchronized (playbinLock) {
586 return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
591 synchronized (playbinLock) {
592 if (gstPlaybin2 != null) {
593 if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
594 logger.log(Level.WARNING,
"Attempt to call PlayBin2.stop() failed.");
595 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
598 if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
599 logger.log(Level.WARNING,
"Attempt to call PlayBin2.setState(State.READY) failed.");
600 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
602 gstPlaybin2.getState();
605 pauseButton.setText(
"â–º");
606 progressSlider.setValue(0);
608 String durationStr = String.format(durationFormat, 0, 0, 0,
609 totalHours, totalMinutes, totalSeconds);
610 progressLabel.setText(durationStr);
620 return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
627 progressSlider.setEnabled(
true);
629 int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
630 ClockTime pos = null;
631 while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
633 synchronized (playbinLock) {
634 pos = gstPlaybin2.queryPosition();
636 millisElapsed = pos.toMillis();
639 long secondsElapsed = millisElapsed / 1000;
640 elapsedHours = (int) secondsElapsed / 3600;
641 secondsElapsed -= elapsedHours * 3600;
642 elapsedMinutes = (int) secondsElapsed / 60;
643 secondsElapsed -= elapsedMinutes * 60;
644 elapsedSeconds = (int) secondsElapsed;
646 String durationStr = String.format(durationFormat,
647 elapsedHours, elapsedMinutes, elapsedSeconds,
648 totalHours, totalMinutes, totalSeconds);
650 progressLabel.setText(durationStr);
652 progressSlider.setValue((
int) millisElapsed);
653 autoTracking =
false;
656 Thread.sleep(INTER_FRAME_PERIOD_MS);
657 }
catch (InterruptedException ex) {
663 progressSlider.setEnabled(
false);
676 }
catch (InterruptedException | ExecutionException ex) {
677 logger.log(Level.WARNING,
"Error updating video progress: " + ex.getMessage());
678 infoLabel.setText(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.progress.infoLabel.updateErr",
682 catch (java.util.concurrent.CancellationException ex ) { }
690 boolean success =
false;
703 return extractedBytes;
709 progress = ProgressHandleFactory.createHandle(
710 NbBundle.getMessage(
GstVideoPanel.class,
"GstVideoPanel.ExtractMedia.progress.buffering", sFile.
getName()),
713 public boolean cancel() {
717 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.progress.buffering"));
719 progress.switchToDeterminate(100);
722 }
catch (IOException ex) {
723 logger.log(Level.WARNING,
"Error buffering file", ex);
734 }
catch (CancellationException ex) {
735 logger.log(Level.INFO,
"Media buffering was canceled.");
736 }
catch (InterruptedException ex) {
737 logger.log(Level.INFO,
"Media buffering was interrupted.");
738 }
catch (Exception ex) {
739 logger.log(Level.SEVERE,
"Fatal error during media buffering.", ex);
742 if (!this.isCancelled()) {
749 if (jFile == null || !jFile.exists()) {
750 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.progressLabel.bufferingErr"));
753 ClockTime dur = null;
754 synchronized (playbinLock) {
756 if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
757 logger.log(Level.WARNING,
"Attempt to call PlayBin2.play() failed.");
758 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
761 if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
762 logger.log(Level.WARNING,
"Attempt to call PlayBin2.pause() failed.");
763 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
766 gstPlaybin2.getState();
767 dur = gstPlaybin2.queryDuration();
769 duration = dur.toString();
770 durationMillis = dur.toMillis();
773 long durationSeconds = (int) durationMillis / 1000;
774 totalHours = (int) durationSeconds / 3600;
775 durationSeconds -= totalHours * 3600;
776 totalMinutes = (int) durationSeconds / 60;
777 durationSeconds -= totalMinutes * 60;
778 totalSeconds = (int) durationSeconds;
780 SwingUtilities.invokeLater(
new Runnable() {
783 progressSlider.setMaximum((
int) durationMillis);
784 progressSlider.setMinimum(0);
786 synchronized (playbinLock) {
787 if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
788 logger.log(Level.WARNING,
"Attempt to call PlayBin2.play() failed.");
789 infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
792 pauseButton.setText(
"||");
793 videoProgressWorker =
new VideoProgressWorker();
794 videoProgressWorker.execute();
List< VideoFrame > captureFrames(java.io.File file, int numFrames)
void customizeComponents()
javax.swing.JLabel progressLabel
JSlider getProgressSlider()
static< T, V > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, SwingWorker< T, V > worker, boolean source)
List< String > getMimeTypes()
boolean isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM flag)
String getTempDirectory()
void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels)
javax.swing.JButton pauseButton
JLabel getProgressLabel()
javax.swing.JPanel videoPanel
synchronized String getUniquePath()
javax.swing.JSlider progressSlider
java.io.File getJFile(AbstractFile file)
void pauseButtonActionPerformed(java.awt.event.ActionEvent evt)
VideoComponent getVideoComponent()
javax.swing.JLabel infoLabel
javax.swing.JPanel controlPanel
VideoProgressWorker videoProgressWorker
volatile PlayBin2 gstPlaybin2
static void error(String title, String message)
static Case getCurrentCase()
VideoComponent gstVideoComponent
FrameCaptureRGBListener(Object waiter)
static Logger getLogger(String name)