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