Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
FXVideoPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-15 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.corecomponents;
20 
21 import com.google.common.io.Files;
22 import java.awt.Dimension;
23 import java.io.File;
24 import java.io.IOException;
25 import java.nio.file.Paths;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.concurrent.CancellationException;
29 import java.util.logging.Level;
30 import javafx.application.Platform;
31 import javafx.beans.Observable;
32 import javafx.concurrent.Task;
33 import javafx.event.ActionEvent;
34 import javafx.event.EventHandler;
35 import javafx.geometry.Insets;
36 import javafx.geometry.Pos;
37 import javafx.scene.Scene;
38 import javafx.scene.control.Button;
39 import javafx.scene.control.Label;
40 import javafx.scene.control.Slider;
41 import javafx.scene.control.Tooltip;
42 import javafx.scene.layout.BorderPane;
43 import javafx.scene.layout.HBox;
44 import javafx.scene.layout.Priority;
45 import javafx.scene.layout.VBox;
46 import javafx.scene.media.Media;
47 import javafx.scene.media.MediaException;
48 import javafx.scene.media.MediaPlayer;
49 import javafx.scene.media.MediaPlayer.Status;
50 import static javafx.scene.media.MediaPlayer.Status.PAUSED;
51 import static javafx.scene.media.MediaPlayer.Status.PLAYING;
52 import static javafx.scene.media.MediaPlayer.Status.READY;
53 import static javafx.scene.media.MediaPlayer.Status.STOPPED;
54 import javafx.scene.media.MediaView;
55 import javafx.util.Duration;
56 import javax.swing.JPanel;
57 import org.netbeans.api.progress.ProgressHandle;
58 import org.netbeans.api.progress.ProgressHandleFactory;
59 import org.openide.util.NbBundle;
60 import org.openide.util.lookup.ServiceProvider;
61 import org.openide.util.lookup.ServiceProviders;
67 import org.sleuthkit.datamodel.AbstractFile;
68 import org.sleuthkit.datamodel.TskCoreException;
69 import org.sleuthkit.datamodel.TskData;
70 
74 @ServiceProviders(value = {
75  @ServiceProvider(service = FrameCapture.class)
76 })
77 public class FXVideoPanel extends MediaViewVideoPanel {
78 
79  // Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html
80  // for Javafx supported formats
81  private static final String[] EXTENSIONS = new String[]{".m4v", ".fxm", ".flv", ".m3u8", ".mp4", ".aif", ".aiff", ".mp3", "m4a", ".wav"}; //NON-NLS
82  private static final List<String> MIMETYPES = Arrays.asList("audio/x-aiff", "video/x-javafx", "video/x-flv", "application/vnd.apple.mpegurl", " audio/mpegurl", "audio/mpeg", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
83  private static final Logger logger = Logger.getLogger(FXVideoPanel.class.getName());
84 
85  private boolean fxInited = false;
86 
88 
89  private AbstractFile currentFile;
90 
91  public FXVideoPanel() {
92  fxInited = Installer.isJavaFxInited();
93  initComponents();
94  if (fxInited) {
95  Platform.runLater(() -> {
96 
97  mediaPane = new MediaPane();
98  Scene fxScene = new Scene(mediaPane);
99  jFXPanel.setScene(fxScene);
100  });
101  }
102  }
103 
104  @Deprecated
105  public JPanel getVideoPanel() {
106  return this;
107  }
108 
109  @Override
110  void setupVideo(final AbstractFile file, final Dimension dims) {
111  if (file.equals(currentFile)) {
112  return;
113  }
114  if (!Case.isCaseOpen()) {
115  //handle in-between condition when case is being closed
116  //and an image was previously selected
117  return;
118  }
119  reset();
120  currentFile = file;
121  final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
122  if (deleted) {
123  mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.mediaPane.infoLabel"));
124  removeAll();
125  return;
126  }
127  mediaPane.setFit(dims);
128 
129  String path = "";
130  try {
131  path = file.getUniquePath();
132  } catch (TskCoreException ex) {
133  logger.log(Level.SEVERE, "Cannot get unique path of video file", ex); //NON-NLS
134  }
135  mediaPane.setInfoLabelText(path);
136  mediaPane.setInfoLabelToolTipText(path);
137 
138  final File tempFile = VideoUtils.getTempVideoFile(currentFile);
139 
140  new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
141 
142  }
143 
144  @Override
145  void reset() {
146  Platform.runLater(() -> {
147  if (mediaPane != null) {
148  mediaPane.reset();
149  }
150  });
151  currentFile = null;
152  }
153 
159  @SuppressWarnings("unchecked")
160  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
161  private void initComponents() {
162 
163  jFXPanel = new javafx.embed.swing.JFXPanel();
164 
165  setBackground(new java.awt.Color(0, 0, 0));
166 
167  javax.swing.GroupLayout jFXPanelLayout = new javax.swing.GroupLayout(jFXPanel);
168  jFXPanel.setLayout(jFXPanelLayout);
169  jFXPanelLayout.setHorizontalGroup(
170  jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
171  .addGap(0, 400, Short.MAX_VALUE)
172  );
173  jFXPanelLayout.setVerticalGroup(
174  jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
175  .addGap(0, 300, Short.MAX_VALUE)
176  );
177 
178  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
179  this.setLayout(layout);
180  layout.setHorizontalGroup(
181  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
182  .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
183  );
184  layout.setVerticalGroup(
185  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
186  .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
187  );
188  }// </editor-fold>//GEN-END:initComponents
189  // Variables declaration - do not modify//GEN-BEGIN:variables
190  private javafx.embed.swing.JFXPanel jFXPanel;
191  // End of variables declaration//GEN-END:variables
192 
193  @Override
194  public boolean isInited() {
195  return fxInited;
196  }
197 
198  private class MediaPane extends BorderPane {
199 
200  private MediaPlayer mediaPlayer;
201 
202  private final MediaView mediaView;
203 
207  private Duration duration;
208 
212  private final HBox mediaTools;
213 
217  private final HBox mediaViewPane;
218 
219  private final VBox controlPanel;
220 
221  private final Slider progressSlider;
222 
223  private final Button pauseButton;
224 
225  private final Button stopButton;
226 
227  private final Label progressLabel;
228 
229  private final Label infoLabel;
230 
231  private int totalHours;
232 
233  private int totalMinutes;
234 
235  private int totalSeconds;
236 
237  private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
238 
239  private static final String PLAY_TEXT = "►";
240 
241  private static final String PAUSE_TEXT = "||";
242 
243  private static final String STOP_TEXT = "X"; //NON-NLS
244 
245  public MediaPane() {
246  // Video Display
247  mediaViewPane = new HBox();
248  mediaViewPane.setStyle("-fx-background-color: black"); //NON-NLS
249  mediaViewPane.setAlignment(Pos.CENTER);
250  mediaView = new MediaView();
251  mediaViewPane.getChildren().add(mediaView);
252  setCenter(mediaViewPane);
253 
254  // Media Controls
255  controlPanel = new VBox();
256  mediaTools = new HBox();
257  mediaTools.setAlignment(Pos.CENTER);
258  mediaTools.setPadding(new Insets(5, 10, 5, 10));
259 
260  pauseButton = new Button(PLAY_TEXT);
261  stopButton = new Button(STOP_TEXT);
262  mediaTools.getChildren().add(pauseButton);
263  mediaTools.getChildren().add(new Label(" "));
264  mediaTools.getChildren().add(stopButton);
265  mediaTools.getChildren().add(new Label(" "));
266  progressSlider = new Slider();
267  HBox.setHgrow(progressSlider, Priority.ALWAYS);
268  progressSlider.setMinWidth(50);
269  progressSlider.setMaxWidth(Double.MAX_VALUE);
270  mediaTools.getChildren().add(progressSlider);
271  progressLabel = new Label();
272  progressLabel.setPrefWidth(135);
273  progressLabel.setMinWidth(135);
274  mediaTools.getChildren().add(progressLabel);
275 
276  controlPanel.getChildren().add(mediaTools);
277  controlPanel.setStyle("-fx-background-color: white"); //NON-NLS
278  infoLabel = new Label("");
279  controlPanel.getChildren().add(infoLabel);
280  setBottom(controlPanel);
281  setProgressActionListeners();
282  }
283 
288  public void reset() {
289  if (mediaPlayer != null) {
290  setInfoLabelText("");
291  if (mediaPlayer.getStatus() == Status.PLAYING) {
292  mediaPlayer.stop();
293  }
294  mediaPlayer = null;
295  mediaView.setMediaPlayer(null);
296  }
297  resetProgress();
298  }
299 
305  public void setInfoLabelText(final String text) {
306  logger.log(Level.INFO, "Setting Info Label Text: {0}", text); //NON-NLS
307  Platform.runLater(() -> {
308  infoLabel.setText(text);
309  });
310  }
311 
317  public void setFit(final Dimension dims) {
318  Platform.runLater(() -> {
319  setPrefSize(dims.getWidth(), dims.getHeight());
320  // Set the Video output to fit the size allocated for it. give an
321  // extra few px to ensure the info label will be shown
322  mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
323  });
324  }
325 
329  private void setProgressActionListeners() {
330  pauseButton.setOnAction(new EventHandler<ActionEvent>() {
331  @Override
332  public void handle(ActionEvent e) {
333  if (mediaPlayer == null) {
334  return;
335  }
336 
337  Status status = mediaPlayer.getStatus();
338 
339  switch (status) {
340  // If playing, pause
341  case PLAYING:
342  mediaPlayer.pause();
343  break;
344  // If ready, paused or stopped, continue playing
345  case READY:
346  case PAUSED:
347  case STOPPED:
348  mediaPlayer.play();
349  break;
350  default:
351  logger.log(Level.INFO, "MediaPlayer in unexpected state: {0}", status.toString()); //NON-NLS
352  // If the MediaPlayer is in an unexpected state, stop playback.
353  mediaPlayer.stop();
354  setInfoLabelText(NbBundle.getMessage(this.getClass(),
355  "FXVideoPanel.pauseButton.infoLabel.playbackErr"));
356  break;
357  }
358  }
359  });
360 
361  stopButton.setOnAction((ActionEvent e) -> {
362  if (mediaPlayer == null) {
363  return;
364  }
365 
366  mediaPlayer.stop();
367  });
368 
369  progressSlider.valueProperty().addListener((Observable o) -> {
370  if (mediaPlayer == null) {
371  return;
372  }
373 
374  if (progressSlider.isValueChanging()) {
375  mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
376  }
377  });
378  }
379 
383  private void resetProgress() {
384  totalHours = 0;
385  totalMinutes = 0;
386  totalSeconds = 0;
387  progressSlider.setValue(0.0);
388  updateTime(Duration.ZERO);
389  }
390 
400  private MediaPlayer createMediaPlayer(String mediaUri) {
401  Media media = new Media(mediaUri);
402 
403  MediaPlayer player = new MediaPlayer(media);
404  player.setOnReady(new ReadyListener());
405  final Runnable pauseListener = () -> {
406  pauseButton.setText(PLAY_TEXT);
407  };
408  player.setOnPaused(pauseListener);
409  player.setOnStopped(pauseListener);
410  player.setOnPlaying(() -> {
411  pauseButton.setText(PAUSE_TEXT);
412  });
413  player.setOnEndOfMedia(new EndOfMediaListener());
414 
415  player.currentTimeProperty().addListener((observable, oldTime, newTime) -> {
416  updateSlider(newTime);
417  updateTime(newTime);
418  });
419 
420  return player;
421  }
422 
427  private void updateProgress() {
428  if (mediaPlayer == null) {
429  return;
430  }
431  Duration currentTime = mediaPlayer.getCurrentTime();
432  updateSlider(currentTime);
433  updateTime(currentTime);
434  }
435 
441  private void updateSlider(Duration currentTime) {
442  if (progressSlider != null) {
443  progressSlider.setDisable(currentTime.isUnknown());
444  if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO)
445  && !progressSlider.isValueChanging()) {
446  progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0);
447  }
448  }
449  }
450 
456  private void updateTime(Duration currentTime) {
457  long millisElapsed = (long) currentTime.toMillis();
458 
459  long elapsedHours, elapsedMinutes, elapsedSeconds;
460  // pick out the elapsed hours, minutes, seconds
461  long secondsElapsed = millisElapsed / 1000;
462  elapsedHours = (int) secondsElapsed / 3600;
463  secondsElapsed -= elapsedHours * 3600;
464  elapsedMinutes = (int) secondsElapsed / 60;
465  secondsElapsed -= elapsedMinutes * 60;
466  elapsedSeconds = (int) secondsElapsed;
467 
468  String durationStr = String.format(durationFormat,
469  elapsedHours, elapsedMinutes, elapsedSeconds,
470  totalHours, totalMinutes, totalSeconds);
471  Platform.runLater(() -> {
472  progressLabel.setText(durationStr);
473  });
474  }
475 
476  private void setInfoLabelToolTipText(final String text) {
477  Platform.runLater(() -> {
478  infoLabel.setTooltip(new Tooltip(text));
479  });
480  }
481 
487  private class ReadyListener implements Runnable {
488 
489  @Override
490  public void run() {
491  if (mediaPlayer == null) {
492  return;
493  }
494 
495  duration = mediaPlayer.getMedia().getDuration();
496  long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis();
497 
498  // pick out the total hours, minutes, seconds
499  long durationSeconds = (int) durationInMillis / 1000;
500  totalHours = (int) durationSeconds / 3600;
501  durationSeconds -= totalHours * 3600;
502  totalMinutes = (int) durationSeconds / 60;
503  durationSeconds -= totalMinutes * 60;
504  totalSeconds = (int) durationSeconds;
505  updateProgress();
506  }
507  }
508 
514  private class EndOfMediaListener implements Runnable {
515 
516  @Override
517  public void run() {
518  if (mediaPlayer == null) {
519  return;
520  }
521 
522  Duration beginning = mediaPlayer.getStartTime();
523  mediaPlayer.stop();
524  mediaPlayer.pause();
525  pauseButton.setText(PLAY_TEXT);
526  updateSlider(beginning);
527  updateTime(beginning);
528  }
529  }
530 
535  private class ExtractMedia extends Task<Long> {
536 
537  private ProgressHandle progress;
538 
539  private final AbstractFile sourceFile;
540 
541  private final java.io.File tempFile;
542 
543  ExtractMedia(AbstractFile sFile, java.io.File jFile) {
544  this.sourceFile = sFile;
545  this.tempFile = jFile;
546  }
547 
553  public String getMediaUri() {
554  return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
555  }
556 
557  @Override
558  protected Long call() throws Exception {
559  if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
560  progress = ProgressHandleFactory.createHandle(
561  NbBundle.getMessage(this.getClass(),
562  "FXVideoPanel.progress.bufferingFile",
563  sourceFile.getName()
564  ),
565  () -> ExtractMedia.this.cancel(true));
566 
567  Platform.runLater(() -> {
568  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.buffering"));
569  });
570 
571  progress.start(100);
572  try {
573  Files.createParentDirs(tempFile);
574  return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
575  } catch (IOException ex) {
576  logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
577  return 0L;
578  } finally {
579  logger.log(Level.INFO, "Done buffering: {0}", tempFile.getName()); //NON-NLS
580  }
581  }
582  return 0L;
583  }
584 
585  @Override
586  protected void failed() {
587  super.failed();
588  onDone();
589  }
590 
591  @Override
592  protected void succeeded() {
593  super.succeeded();
594  onDone();
595  }
596 
597  @Override
598  protected void cancelled() {
599  super.cancelled();
600  onDone();
601  }
602 
603  private void onDone() {
604  progressLabel.setText("");
605  try {
606  super.get(); //block and get all exceptions thrown while doInBackground()
607  } catch (CancellationException ex) {
608  logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
609  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingCancelled"));
610  } catch (InterruptedException ex) {
611  logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
612  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingInterrupted"));
613  } catch (Exception ex) {
614  logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
615  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.errorWritingVideoToDisk"));
616  } finally {
617  if (null != progress) {
618  progress.finish();
619  }
620  if (!this.isCancelled()) {
621  logger.log(Level.INFO, "ExtractMedia is done: {0}", tempFile.getName()); //NON-NLS
622  try {
623  mediaPane.mediaPlayer = mediaPane.createMediaPlayer(getMediaUri());
624  mediaView.setMediaPlayer(mediaPane.mediaPlayer);
625  } catch (MediaException ex) {
626  progressLabel.setText("");
627  mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat"));
628  }
629  }
630  }
631  }
632  }
633  }
634 
645  @Override
646  public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
647  //What is/was the point of this method /interface.
648  return null;
649  }
650 
651  @Override
652  public String[] getExtensions() {
653  return EXTENSIONS.clone();
654  }
655 
656  @Override
657  public List<String> getMimeTypes() {
658  return MIMETYPES;
659  }
660 }
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
List< VideoFrame > captureFrames(java.io.File file, int numFrames)
synchronized static Logger getLogger(String name)
Definition: Logger.java:166

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.