Autopsy  4.6.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-2018 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.contentviewers;
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.openide.util.NbBundle;
59 import org.openide.util.lookup.ServiceProvider;
60 import org.openide.util.lookup.ServiceProviders;
69 import org.sleuthkit.datamodel.AbstractFile;
70 import org.sleuthkit.datamodel.TskCoreException;
71 import org.sleuthkit.datamodel.TskData;
72 
76 @ServiceProviders(value = {
77  @ServiceProvider(service = FrameCapture.class)
78 })
79 public class FXVideoPanel extends MediaViewVideoPanel {
80 
81  // Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html
82  // for Javafx supported formats
83  private static final String[] EXTENSIONS = new String[]{".m4v", ".fxm", ".flv", ".m3u8", ".mp4", ".aif", ".aiff", ".mp3", "m4a", ".wav"}; //NON-NLS
84  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
85  private static final Logger logger = Logger.getLogger(FXVideoPanel.class.getName());
86 
87  private boolean fxInited = false;
88 
90 
91  private AbstractFile currentFile;
92 
93  public FXVideoPanel() {
94  fxInited = Installer.isJavaFxInited();
95  initComponents();
96  if (fxInited) {
97  Platform.runLater(() -> {
98 
99  mediaPane = new MediaPane();
100  Scene fxScene = new Scene(mediaPane);
101  jFXPanel.setScene(fxScene);
102  });
103  }
104  }
105 
106  @Deprecated
107  public JPanel getVideoPanel() {
108  return this;
109  }
110 
111  @Override
112  void setupVideo(final AbstractFile file, final Dimension dims) {
113  if (file.equals(currentFile)) {
114  return;
115  }
116  if (!Case.isCaseOpen()) {
117  //handle in-between condition when case is being closed
118  //and an image was previously selected
119  return;
120  }
121  reset();
122  currentFile = file;
123  final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
124  if (deleted) {
125  mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.mediaPane.infoLabel"));
126  removeAll();
127  return;
128  }
129  mediaPane.setFit(dims);
130 
131  String path = "";
132  try {
133  path = file.getUniquePath();
134  } catch (TskCoreException ex) {
135  logger.log(Level.SEVERE, "Cannot get unique path of video file", ex); //NON-NLS
136  }
137  mediaPane.setInfoLabelText(path);
138  mediaPane.setInfoLabelToolTipText(path);
139 
140  final File tempFile;
141  try {
142  tempFile = VideoUtils.getTempVideoFile(currentFile);
143  } catch (NoCurrentCaseException ex) {
144  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
145  return;
146  }
147 
148  new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
149 
150  }
151 
152  @Override
153  void reset() {
154  Platform.runLater(() -> {
155  if (mediaPane != null) {
156  mediaPane.reset();
157  }
158  });
159  currentFile = null;
160  }
161 
167  @SuppressWarnings("unchecked")
168  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
169  private void initComponents() {
170 
171  jFXPanel = new javafx.embed.swing.JFXPanel();
172 
173  setBackground(new java.awt.Color(0, 0, 0));
174 
175  javax.swing.GroupLayout jFXPanelLayout = new javax.swing.GroupLayout(jFXPanel);
176  jFXPanel.setLayout(jFXPanelLayout);
177  jFXPanelLayout.setHorizontalGroup(
178  jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
179  .addGap(0, 400, Short.MAX_VALUE)
180  );
181  jFXPanelLayout.setVerticalGroup(
182  jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
183  .addGap(0, 300, Short.MAX_VALUE)
184  );
185 
186  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
187  this.setLayout(layout);
188  layout.setHorizontalGroup(
189  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
190  .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
191  );
192  layout.setVerticalGroup(
193  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
194  .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
195  );
196  }// </editor-fold>//GEN-END:initComponents
197  // Variables declaration - do not modify//GEN-BEGIN:variables
198  private javafx.embed.swing.JFXPanel jFXPanel;
199  // End of variables declaration//GEN-END:variables
200 
201  @Override
202  public boolean isInited() {
203  return fxInited;
204  }
205 
206  private class MediaPane extends BorderPane {
207 
208  private MediaPlayer mediaPlayer;
209 
210  private final MediaView mediaView;
211 
215  private Duration duration;
216 
220  private final HBox mediaTools;
221 
225  private final HBox mediaViewPane;
226 
227  private final VBox controlPanel;
228 
229  private final Slider progressSlider;
230 
231  private final Button pauseButton;
232 
233  private final Button stopButton;
234 
235  private final Label progressLabel;
236 
237  private final Label infoLabel;
238 
239  private int totalHours;
240 
241  private int totalMinutes;
242 
243  private int totalSeconds;
244 
245  private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
246 
247  private static final String PLAY_TEXT = "►";
248 
249  private static final String PAUSE_TEXT = "||";
250 
251  private static final String STOP_TEXT = "X"; //NON-NLS
252 
253  public MediaPane() {
254  // Video Display
255  mediaViewPane = new HBox();
256  mediaViewPane.setStyle("-fx-background-color: black"); //NON-NLS
257  mediaViewPane.setAlignment(Pos.CENTER);
258  mediaView = new MediaView();
259  mediaViewPane.getChildren().add(mediaView);
260  setCenter(mediaViewPane);
261 
262  // Media Controls
263  controlPanel = new VBox();
264  mediaTools = new HBox();
265  mediaTools.setAlignment(Pos.CENTER);
266  mediaTools.setPadding(new Insets(5, 10, 5, 10));
267 
268  pauseButton = new Button(PLAY_TEXT);
269  stopButton = new Button(STOP_TEXT);
270  mediaTools.getChildren().add(pauseButton);
271  mediaTools.getChildren().add(new Label(" "));
272  mediaTools.getChildren().add(stopButton);
273  mediaTools.getChildren().add(new Label(" "));
274  progressSlider = new Slider();
275  HBox.setHgrow(progressSlider, Priority.ALWAYS);
276  progressSlider.setMinWidth(50);
277  progressSlider.setMaxWidth(Double.MAX_VALUE);
278  mediaTools.getChildren().add(progressSlider);
279  progressLabel = new Label();
280  progressLabel.setPrefWidth(135);
281  progressLabel.setMinWidth(135);
282  mediaTools.getChildren().add(progressLabel);
283 
284  controlPanel.getChildren().add(mediaTools);
285  controlPanel.setStyle("-fx-background-color: white"); //NON-NLS
286  infoLabel = new Label("");
287  controlPanel.getChildren().add(infoLabel);
288  setBottom(controlPanel);
289  setProgressActionListeners();
290  }
291 
296  public void reset() {
297  if (mediaPlayer != null) {
298  setInfoLabelText("");
299  if (mediaPlayer.getStatus() == Status.PLAYING) {
300  mediaPlayer.stop();
301  }
302  mediaPlayer = null;
303  mediaView.setMediaPlayer(null);
304  }
305  resetProgress();
306  }
307 
313  public void setInfoLabelText(final String text) {
314  logger.log(Level.INFO, "Setting Info Label Text: {0}", text); //NON-NLS
315  Platform.runLater(() -> {
316  infoLabel.setText(text);
317  });
318  }
319 
325  public void setFit(final Dimension dims) {
326  Platform.runLater(() -> {
327  setPrefSize(dims.getWidth(), dims.getHeight());
328  // Set the Video output to fit the size allocated for it. give an
329  // extra few px to ensure the info label will be shown
330  mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
331  });
332  }
333 
337  private void setProgressActionListeners() {
338  pauseButton.setOnAction(new EventHandler<ActionEvent>() {
339  @Override
340  public void handle(ActionEvent e) {
341  if (mediaPlayer == null) {
342  return;
343  }
344 
345  Status status = mediaPlayer.getStatus();
346 
347  switch (status) {
348  // If playing, pause
349  case PLAYING:
350  mediaPlayer.pause();
351  break;
352  // If ready, paused or stopped, continue playing
353  case READY:
354  case PAUSED:
355  case STOPPED:
356  mediaPlayer.play();
357  break;
358  default:
359  logger.log(Level.INFO, "MediaPlayer in unexpected state: {0}", status.toString()); //NON-NLS
360  // If the MediaPlayer is in an unexpected state, stop playback.
361  mediaPlayer.stop();
362  setInfoLabelText(NbBundle.getMessage(this.getClass(),
363  "FXVideoPanel.pauseButton.infoLabel.playbackErr"));
364  break;
365  }
366  }
367  });
368 
369  stopButton.setOnAction((ActionEvent e) -> {
370  if (mediaPlayer == null) {
371  return;
372  }
373 
374  mediaPlayer.stop();
375  });
376 
377  progressSlider.valueProperty().addListener((Observable o) -> {
378  if (mediaPlayer == null) {
379  return;
380  }
381 
382  if (progressSlider.isValueChanging()) {
383  mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
384  }
385  });
386  }
387 
391  private void resetProgress() {
392  totalHours = 0;
393  totalMinutes = 0;
394  totalSeconds = 0;
395  progressSlider.setValue(0.0);
396  updateTime(Duration.ZERO);
397  }
398 
408  private MediaPlayer createMediaPlayer(String mediaUri) {
409  Media media = new Media(mediaUri);
410 
411  MediaPlayer player = new MediaPlayer(media);
412  player.setOnReady(new ReadyListener());
413  final Runnable pauseListener = () -> {
414  pauseButton.setText(PLAY_TEXT);
415  };
416  player.setOnPaused(pauseListener);
417  player.setOnStopped(pauseListener);
418  player.setOnPlaying(() -> {
419  pauseButton.setText(PAUSE_TEXT);
420  });
421  player.setOnEndOfMedia(new EndOfMediaListener());
422 
423  player.currentTimeProperty().addListener((observable, oldTime, newTime) -> {
424  updateSlider(newTime);
425  updateTime(newTime);
426  });
427 
428  return player;
429  }
430 
435  private void updateProgress() {
436  if (mediaPlayer == null) {
437  return;
438  }
439  Duration currentTime = mediaPlayer.getCurrentTime();
440  updateSlider(currentTime);
441  updateTime(currentTime);
442  }
443 
449  private void updateSlider(Duration currentTime) {
450  if (progressSlider != null) {
451  progressSlider.setDisable(currentTime.isUnknown());
452  if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO)
453  && !progressSlider.isValueChanging()) {
454  progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0);
455  }
456  }
457  }
458 
464  private void updateTime(Duration currentTime) {
465  long millisElapsed = (long) currentTime.toMillis();
466 
467  long elapsedHours, elapsedMinutes, elapsedSeconds;
468  // pick out the elapsed hours, minutes, seconds
469  long secondsElapsed = millisElapsed / 1000;
470  elapsedHours = (int) secondsElapsed / 3600;
471  secondsElapsed -= elapsedHours * 3600;
472  elapsedMinutes = (int) secondsElapsed / 60;
473  secondsElapsed -= elapsedMinutes * 60;
474  elapsedSeconds = (int) secondsElapsed;
475 
476  String durationStr = String.format(durationFormat,
477  elapsedHours, elapsedMinutes, elapsedSeconds,
478  totalHours, totalMinutes, totalSeconds);
479  Platform.runLater(() -> {
480  progressLabel.setText(durationStr);
481  });
482  }
483 
484  private void setInfoLabelToolTipText(final String text) {
485  Platform.runLater(() -> {
486  infoLabel.setTooltip(new Tooltip(text));
487  });
488  }
489 
495  private class ReadyListener implements Runnable {
496 
497  @Override
498  public void run() {
499  if (mediaPlayer == null) {
500  return;
501  }
502 
503  duration = mediaPlayer.getMedia().getDuration();
504  long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis();
505 
506  // pick out the total hours, minutes, seconds
507  long durationSeconds = (int) durationInMillis / 1000;
508  totalHours = (int) durationSeconds / 3600;
509  durationSeconds -= totalHours * 3600;
510  totalMinutes = (int) durationSeconds / 60;
511  durationSeconds -= totalMinutes * 60;
512  totalSeconds = (int) durationSeconds;
513  updateProgress();
514  }
515  }
516 
522  private class EndOfMediaListener implements Runnable {
523 
524  @Override
525  public void run() {
526  if (mediaPlayer == null) {
527  return;
528  }
529 
530  Duration beginning = mediaPlayer.getStartTime();
531  mediaPlayer.stop();
532  mediaPlayer.pause();
533  pauseButton.setText(PLAY_TEXT);
534  updateSlider(beginning);
535  updateTime(beginning);
536  }
537  }
538 
543  private class ExtractMedia extends Task<Long> {
544 
545  private ProgressHandle progress;
546 
547  private final AbstractFile sourceFile;
548 
549  private final java.io.File tempFile;
550 
551  ExtractMedia(AbstractFile sFile, java.io.File jFile) {
552  this.sourceFile = sFile;
553  this.tempFile = jFile;
554  }
555 
561  public String getMediaUri() {
562  return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
563  }
564 
565  @Override
566  protected Long call() throws Exception {
567  if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
568  progress = ProgressHandle.createHandle(
569  NbBundle.getMessage(this.getClass(),
570  "FXVideoPanel.progress.bufferingFile",
571  sourceFile.getName()
572  ),
573  () -> ExtractMedia.this.cancel(true));
574 
575  Platform.runLater(() -> {
576  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.buffering"));
577  });
578 
579  progress.start(100);
580  try {
581  Files.createParentDirs(tempFile);
582  return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
583  } catch (IOException ex) {
584  logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
585  return 0L;
586  } finally {
587  logger.log(Level.INFO, "Done buffering: {0}", tempFile.getName()); //NON-NLS
588  }
589  }
590  return 0L;
591  }
592 
593  @Override
594  protected void failed() {
595  super.failed();
596  onDone();
597  }
598 
599  @Override
600  protected void succeeded() {
601  super.succeeded();
602  onDone();
603  }
604 
605  @Override
606  protected void cancelled() {
607  super.cancelled();
608  onDone();
609  }
610 
611  private void onDone() {
612  progressLabel.setText("");
613  try {
614  super.get(); //block and get all exceptions thrown while doInBackground()
615  } catch (CancellationException ex) {
616  logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
617  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingCancelled"));
618  } catch (InterruptedException ex) {
619  logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
620  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingInterrupted"));
621  } catch (Exception ex) {
622  logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
623  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.errorWritingVideoToDisk"));
624  } finally {
625  if (null != progress) {
626  progress.finish();
627  }
628  if (!this.isCancelled()) {
629  logger.log(Level.INFO, "ExtractMedia is done: {0}", tempFile.getName()); //NON-NLS
630  try {
631  mediaPane.mediaPlayer = mediaPane.createMediaPlayer(getMediaUri());
632  mediaView.setMediaPlayer(mediaPane.mediaPlayer);
633  } catch (MediaException ex) {
634  progressLabel.setText("");
635  mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat"));
636  }
637  }
638  }
639  }
640  }
641  }
642 
653  @Override
654  public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
655  //What is/was the point of this method /interface.
656  return null;
657  }
658 
659  @Override
660  public String[] getExtensions() {
661  return EXTENSIONS.clone();
662  }
663 
664  @Override
665  public List<String> getMimeTypes() {
666  return MIMETYPES;
667  }
668 }
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:124

Copyright © 2012-2016 Basis Technology. Generated on: Mon May 7 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.