mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
commit
18f2843acc
@ -8,7 +8,7 @@ FXVideoPanel.progress.bufferingFile=Buffering {0}
|
|||||||
FXVideoPanel.progressLabel.buffering=Buffering...
|
FXVideoPanel.progressLabel.buffering=Buffering...
|
||||||
FXVideoPanel.media.unsupportedFormat=Unsupported Format.
|
FXVideoPanel.media.unsupportedFormat=Unsupported Format.
|
||||||
GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
|
GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
|
||||||
GstVideoPanel.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
||||||
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
|
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
|
||||||
GstVideoPanel.exception.problemFile.msg=Cannot capture frames from this file ({0}).
|
GstVideoPanel.exception.problemFile.msg=Cannot capture frames from this file ({0}).
|
||||||
GstVideoPanel.exception.problemPlay.msg=Problem with video file; problem when attempting to play while obtaining duration.
|
GstVideoPanel.exception.problemPlay.msg=Problem with video file; problem when attempting to play while obtaining duration.
|
||||||
@ -84,3 +84,8 @@ MediaViewImagePanel.zoomTextField.text=
|
|||||||
MediaViewImagePanel.rotationTextField.text=
|
MediaViewImagePanel.rotationTextField.text=
|
||||||
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
||||||
HtmlPanel.showImagesToggleButton.text=Show Images
|
HtmlPanel.showImagesToggleButton.text=Show Images
|
||||||
|
MediaPlayerPanel.audioSlider.toolTipText=
|
||||||
|
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
|
||||||
|
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
|
||||||
|
MediaPlayerPanel.playButton.text=\u25ba
|
||||||
|
MediaPlayerPanel.infoLabel.text=No Errors
|
||||||
|
@ -19,8 +19,12 @@ FXVideoPanel.progress.bufferingFile=Buffering {0}
|
|||||||
FXVideoPanel.progressLabel.buffering=Buffering...
|
FXVideoPanel.progressLabel.buffering=Buffering...
|
||||||
FXVideoPanel.media.unsupportedFormat=Unsupported Format.
|
FXVideoPanel.media.unsupportedFormat=Unsupported Format.
|
||||||
GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
|
GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
|
||||||
GstVideoPanel.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
|
||||||
GstVideoPanel.noOpenCase.errMsg=No open case available.
|
GstVideoPanel.noOpenCase.errMsg=No open case available.
|
||||||
|
Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.
|
||||||
|
HtmlPanel_showImagesToggleButton_hide=Hide Images
|
||||||
|
HtmlPanel_showImagesToggleButton_show=Show Images
|
||||||
|
HtmlViewer_file_error=This file is missing or unreadable.
|
||||||
|
MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
||||||
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
|
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
|
||||||
GstVideoPanel.exception.problemFile.msg=Cannot capture frames from this file ({0}).
|
GstVideoPanel.exception.problemFile.msg=Cannot capture frames from this file ({0}).
|
||||||
GstVideoPanel.exception.problemPlay.msg=Problem with video file; problem when attempting to play while obtaining duration.
|
GstVideoPanel.exception.problemPlay.msg=Problem with video file; problem when attempting to play while obtaining duration.
|
||||||
@ -32,13 +36,12 @@ GstVideoPanel.progress.buffering=Buffering...
|
|||||||
GstVideoPanel.progressLabel.bufferingErr=Error buffering file
|
GstVideoPanel.progressLabel.bufferingErr=Error buffering file
|
||||||
GstVideoPanel.progress.infoLabel.updateErr=Error updating video progress: {0}
|
GstVideoPanel.progress.infoLabel.updateErr=Error updating video progress: {0}
|
||||||
GstVideoPanel.ExtractMedia.progress.buffering=Buffering {0}
|
GstVideoPanel.ExtractMedia.progress.buffering=Buffering {0}
|
||||||
Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.
|
|
||||||
HtmlPanel_showImagesToggleButton_hide=Hide Images
|
|
||||||
HtmlPanel_showImagesToggleButton_show=Show Images
|
|
||||||
HtmlViewer_file_error=This file is missing or unreadable.
|
|
||||||
MediaFileViewer.AccessibleContext.accessibleDescription=
|
MediaFileViewer.AccessibleContext.accessibleDescription=
|
||||||
MediaFileViewer.title=Media
|
MediaFileViewer.title=Media
|
||||||
MediaFileViewer.toolTip=Displays supported multimedia files (images, videos, audio)
|
MediaFileViewer.toolTip=Displays supported multimedia files (images, videos, audio)
|
||||||
|
MediaPlayerPanel.noSupport=File not supported.
|
||||||
|
MediaPlayerPanel.timeFormat=%02d:%02d:%02d
|
||||||
|
MediaPlayerPanel.unknownTime=Unknown
|
||||||
MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory.
|
MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory.
|
||||||
MediaViewImagePanel.errorLabel.text=Could not load file into Media View.
|
MediaViewImagePanel.errorLabel.text=Could not load file into Media View.
|
||||||
MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E
|
MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E
|
||||||
@ -143,6 +146,11 @@ MediaViewImagePanel.zoomTextField.text=
|
|||||||
MediaViewImagePanel.rotationTextField.text=
|
MediaViewImagePanel.rotationTextField.text=
|
||||||
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
||||||
HtmlPanel.showImagesToggleButton.text=Show Images
|
HtmlPanel.showImagesToggleButton.text=Show Images
|
||||||
|
MediaPlayerPanel.audioSlider.toolTipText=
|
||||||
|
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
|
||||||
|
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
|
||||||
|
MediaPlayerPanel.playButton.text=\u25ba
|
||||||
|
MediaPlayerPanel.infoLabel.text=No Errors
|
||||||
# {0} - tableName
|
# {0} - tableName
|
||||||
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
|
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
|
||||||
# {0} - tableName
|
# {0} - tableName
|
||||||
|
@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Autopsy Forensic Browser
|
|
||||||
*
|
|
||||||
* Copyright 2019 Basis Technology Corp.
|
|
||||||
* Contact: carrier <at> sleuthkit <dot> 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.contentviewers;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.embed.swing.JFXPanel;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.image.PixelFormat;
|
|
||||||
import javafx.scene.image.PixelWriter;
|
|
||||||
import javafx.scene.image.WritableImage;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
|
||||||
import org.freedesktop.gstreamer.Buffer;
|
|
||||||
import org.freedesktop.gstreamer.Caps;
|
|
||||||
import org.freedesktop.gstreamer.FlowReturn;
|
|
||||||
import org.freedesktop.gstreamer.Sample;
|
|
||||||
import org.freedesktop.gstreamer.Structure;
|
|
||||||
import org.freedesktop.gstreamer.elements.AppSink;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a video renderer for GStreamer.
|
|
||||||
*/
|
|
||||||
final class GstVideoRendererPanel extends JFXPanel {
|
|
||||||
|
|
||||||
private static final String CAP_MIME_TYPE = "video/x-raw";
|
|
||||||
private static final String CAP_BYTE_ORDER = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? "format=BGRx" : "format=xRGB");
|
|
||||||
private static final int PROP_MAX_BUFFERS = 5000;
|
|
||||||
private AppSink videoSink;
|
|
||||||
private ImageView fxImageView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance.
|
|
||||||
*/
|
|
||||||
GstVideoRendererPanel() {
|
|
||||||
initImageView();
|
|
||||||
initVideoSink();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the ImageView to show the current frame.
|
|
||||||
*/
|
|
||||||
private void initImageView() {
|
|
||||||
fxImageView = new ImageView(); // Will hold the current video frame.
|
|
||||||
BorderPane borderpane = new BorderPane(fxImageView); // Center and size ImageView.
|
|
||||||
Scene scene = new Scene(borderpane); // Root of the JavaFX tree.
|
|
||||||
setScene(scene);
|
|
||||||
|
|
||||||
// Bind size of image to that of scene, while keeping proportions
|
|
||||||
fxImageView.fitWidthProperty().bind(scene.widthProperty());
|
|
||||||
fxImageView.fitHeightProperty().bind(scene.heightProperty());
|
|
||||||
fxImageView.setPreserveRatio(true);
|
|
||||||
fxImageView.setSmooth(true);
|
|
||||||
fxImageView.setCache(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the video sink.
|
|
||||||
*/
|
|
||||||
private void initVideoSink() {
|
|
||||||
videoSink = new AppSink("GstVideoComponent");
|
|
||||||
videoSink.set("emit-signals", true);
|
|
||||||
AppSinkListener gstListener = new AppSinkListener();
|
|
||||||
videoSink.connect(gstListener);
|
|
||||||
videoSink.setCaps(new Caps(
|
|
||||||
String.format("%s, %s", CAP_MIME_TYPE, CAP_BYTE_ORDER)));
|
|
||||||
videoSink.set("max-buffers", PROP_MAX_BUFFERS);
|
|
||||||
videoSink.set("drop", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the video sink.
|
|
||||||
*
|
|
||||||
* @return The video sink.
|
|
||||||
*/
|
|
||||||
AppSink getVideoSink() {
|
|
||||||
return videoSink;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen for NEW_SAMPLE events to update the ImageView with the newest
|
|
||||||
* video frame.
|
|
||||||
*/
|
|
||||||
class AppSinkListener implements AppSink.NEW_SAMPLE {
|
|
||||||
|
|
||||||
private Image videoFrame;
|
|
||||||
private int lastWidth = 0;
|
|
||||||
private int lastHeight = 0;
|
|
||||||
private byte[] byteArray;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FlowReturn newSample(AppSink appSink) {
|
|
||||||
Sample sample = appSink.pullSample();
|
|
||||||
Buffer buffer = sample.getBuffer();
|
|
||||||
ByteBuffer byteBuffer = buffer.map(false);
|
|
||||||
if (byteBuffer != null) {
|
|
||||||
Structure capsStruct = sample.getCaps().getStructure(0);
|
|
||||||
int width = capsStruct.getInteger("width");
|
|
||||||
int height = capsStruct.getInteger("height");
|
|
||||||
if (width != lastWidth || height != lastHeight) {
|
|
||||||
lastWidth = width;
|
|
||||||
lastHeight = height;
|
|
||||||
byteArray = new byte[width * height * 4];
|
|
||||||
}
|
|
||||||
byteBuffer.get(byteArray);
|
|
||||||
videoFrame = convertBytesToImage(byteArray, width, height);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
fxImageView.setImage(videoFrame);
|
|
||||||
});
|
|
||||||
buffer.unmap();
|
|
||||||
}
|
|
||||||
sample.dispose();
|
|
||||||
|
|
||||||
return FlowReturn.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an image from a byte array of pixels.
|
|
||||||
*
|
|
||||||
* @param pixels The byte array of pixels.
|
|
||||||
* @param width The width of the image.
|
|
||||||
* @param height The height of the image.
|
|
||||||
*
|
|
||||||
* @return The image.
|
|
||||||
*/
|
|
||||||
private Image convertBytesToImage(byte[] pixels, int width, int height) {
|
|
||||||
WritableImage image = new WritableImage(width, height);
|
|
||||||
PixelWriter pixelWriter = image.getPixelWriter();
|
|
||||||
pixelWriter.setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), pixels, 0, width * 4);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
178
Core/src/org/sleuthkit/autopsy/contentviewers/JavaFxAppSink.java
Executable file
178
Core/src/org/sleuthkit/autopsy/contentviewers/JavaFxAppSink.java
Executable file
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> 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.contentviewers;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import javafx.embed.swing.JFXPanel;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.image.PixelFormat;
|
||||||
|
import javafx.scene.image.PixelWriter;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import org.freedesktop.gstreamer.Buffer;
|
||||||
|
import org.freedesktop.gstreamer.Caps;
|
||||||
|
import org.freedesktop.gstreamer.FlowReturn;
|
||||||
|
import org.freedesktop.gstreamer.Sample;
|
||||||
|
import org.freedesktop.gstreamer.Structure;
|
||||||
|
import org.freedesktop.gstreamer.elements.AppSink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a JavaFX Video renderer for GStreamer
|
||||||
|
*/
|
||||||
|
final class JavaFxAppSink extends AppSink {
|
||||||
|
|
||||||
|
private static final String CAP_MIME_TYPE = "video/x-raw";
|
||||||
|
private static final String CAP_BYTE_ORDER = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? "format=BGRx" : "format=xRGB");
|
||||||
|
private static final int PROP_MAX_BUFFERS = 5000;
|
||||||
|
|
||||||
|
private final JavaFxFrameUpdater updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AppSink that hooks an ImageView into a JFXPanel. This AppSink
|
||||||
|
* comes prepackaged with an AppSink listener to accomplish the previous statement.
|
||||||
|
*
|
||||||
|
* @param name AppSink internal name
|
||||||
|
* @param target JFXPanel to display video playback in
|
||||||
|
*/
|
||||||
|
public JavaFxAppSink(String name, JFXPanel target) {
|
||||||
|
super(name);
|
||||||
|
set("emit-signals", true);
|
||||||
|
updater = new JavaFxFrameUpdater(target);
|
||||||
|
connect((AppSink.NEW_SAMPLE) updater);
|
||||||
|
connect((AppSink.NEW_PREROLL) updater);
|
||||||
|
setCaps(new Caps(
|
||||||
|
String.format("%s, %s", CAP_MIME_TYPE, CAP_BYTE_ORDER)));
|
||||||
|
set("max-buffers", PROP_MAX_BUFFERS);
|
||||||
|
set("drop", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the current frame in the JFXPanel
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
disconnect((AppSink.NEW_SAMPLE) updater);
|
||||||
|
disconnect((AppSink.NEW_PREROLL) updater);
|
||||||
|
updater.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for keeping the ImageView that is hooked into the JFXPanel up-to-date
|
||||||
|
* with the most current or available frame from GStreamer.
|
||||||
|
*/
|
||||||
|
static class JavaFxFrameUpdater implements AppSink.NEW_SAMPLE, AppSink.NEW_PREROLL {
|
||||||
|
private final ImageView fxImageView;
|
||||||
|
|
||||||
|
public JavaFxFrameUpdater(JFXPanel target) {
|
||||||
|
//We should probably pass an ImageView instead of a JFXPanel to make
|
||||||
|
//it more reuseable
|
||||||
|
fxImageView = new ImageView(); // Will hold the current video frame.
|
||||||
|
BorderPane borderpane = new BorderPane(fxImageView); // Center and size ImageView.
|
||||||
|
Scene scene = new Scene(borderpane); // Root of the JavaFX tree.
|
||||||
|
target.setScene(scene);
|
||||||
|
|
||||||
|
// Bind size of image to that of scene, while keeping proportions
|
||||||
|
fxImageView.fitWidthProperty().bind(scene.widthProperty());
|
||||||
|
fxImageView.fitHeightProperty().bind(scene.heightProperty());
|
||||||
|
fxImageView.setPreserveRatio(true);
|
||||||
|
fxImageView.setSmooth(true);
|
||||||
|
fxImageView.setCache(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the ImageView when a brand new frame is in the pipeline.
|
||||||
|
*
|
||||||
|
* @param appSink Pipeline containing the new frame
|
||||||
|
* @return Result of update
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public FlowReturn newSample(AppSink appSink) {
|
||||||
|
return setSample(appSink.pullSample());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the ImageView to the input sample. Sample here is synonymous with
|
||||||
|
* frame.
|
||||||
|
*
|
||||||
|
* @param input Frame
|
||||||
|
* @return Result of update
|
||||||
|
*/
|
||||||
|
public FlowReturn setSample(Sample input) {
|
||||||
|
Buffer buffer = input.getBuffer();
|
||||||
|
ByteBuffer byteBuffer = buffer.map(false);
|
||||||
|
if (byteBuffer != null) {
|
||||||
|
Structure capsStruct = input.getCaps().getStructure(0);
|
||||||
|
int width = capsStruct.getInteger("width");
|
||||||
|
int height = capsStruct.getInteger("height");
|
||||||
|
byte[] byteArray = new byte[width * height * 4];
|
||||||
|
byteBuffer.get(byteArray);
|
||||||
|
Image videoFrame = convertBytesToImage(byteArray, width, height);
|
||||||
|
fxImageView.setImage(videoFrame);
|
||||||
|
buffer.unmap();
|
||||||
|
}
|
||||||
|
input.dispose();
|
||||||
|
|
||||||
|
//Keep frames rolling
|
||||||
|
return FlowReturn.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the ImageView with the next frame in the pipeline, without
|
||||||
|
* removing it. This function is invoked when Gstreamer is not in a
|
||||||
|
* PLAYING state, but we can peek at what's to come.
|
||||||
|
*
|
||||||
|
* It's essential for displaying the initial frame when a video is first
|
||||||
|
* selected.
|
||||||
|
*
|
||||||
|
* @param sink Pipeline containing video data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public FlowReturn newPreroll(AppSink sink) {
|
||||||
|
//Grab the next frame without removing it from the pipeline
|
||||||
|
Sample sample = sink.pullPreroll();
|
||||||
|
return setSample(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an image from a byte array of pixels.
|
||||||
|
*
|
||||||
|
* @param pixels The byte array of pixels.
|
||||||
|
* @param width The width of the image.
|
||||||
|
* @param height The height of the image.
|
||||||
|
*
|
||||||
|
* @return The image.
|
||||||
|
*/
|
||||||
|
private Image convertBytesToImage(byte[] pixels, int width, int height) {
|
||||||
|
WritableImage image = new WritableImage(width, height);
|
||||||
|
PixelWriter pixelWriter = image.getPixelWriter();
|
||||||
|
pixelWriter.setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), pixels, 0, width * 4);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the current frame from the display
|
||||||
|
*/
|
||||||
|
void clear() {
|
||||||
|
fxImageView.setImage(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,11 +20,13 @@ package org.sleuthkit.autopsy.contentviewers;
|
|||||||
|
|
||||||
import java.awt.CardLayout;
|
import java.awt.CardLayout;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import org.freedesktop.gstreamer.GstException;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,8 +38,7 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName());
|
||||||
private AbstractFile lastFile;
|
private AbstractFile lastFile;
|
||||||
//UI
|
//UI
|
||||||
private final MediaPlayerPanel mediaPlayerPanel;
|
private MediaPlayerPanel mediaPlayerPanel;
|
||||||
private final boolean mediaPlayerPanelInited;
|
|
||||||
private final MediaViewImagePanel imagePanel;
|
private final MediaViewImagePanel imagePanel;
|
||||||
private final boolean imagePanelInited;
|
private final boolean imagePanelInited;
|
||||||
|
|
||||||
@ -51,10 +52,14 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
|
|
||||||
initComponents();
|
initComponents();
|
||||||
|
|
||||||
// get the right panel for our platform
|
try {
|
||||||
mediaPlayerPanel = new MediaPlayerPanel();
|
mediaPlayerPanel = new MediaPlayerPanel();
|
||||||
mediaPlayerPanelInited = mediaPlayerPanel.isInited();
|
} catch (GstException | UnsatisfiedLinkError ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", ex); //NON-NLS
|
||||||
|
MessageNotifyUtil.Notify.error(
|
||||||
|
NbBundle.getMessage(this.getClass(), "MediaFileViewer.initGst.gstException.msg"),
|
||||||
|
ex.getMessage());
|
||||||
|
}
|
||||||
imagePanel = new MediaViewImagePanel();
|
imagePanel = new MediaViewImagePanel();
|
||||||
imagePanelInited = imagePanel.isInited();
|
imagePanelInited = imagePanel.isInited();
|
||||||
|
|
||||||
@ -64,7 +69,10 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
|
|
||||||
private void customizeComponents() {
|
private void customizeComponents() {
|
||||||
add(imagePanel, IMAGE_VIEWER_LAYER);
|
add(imagePanel, IMAGE_VIEWER_LAYER);
|
||||||
|
|
||||||
|
if(mediaPlayerPanel != null) {
|
||||||
add(mediaPlayerPanel, MEDIA_PLAYER_LAYER);
|
add(mediaPlayerPanel, MEDIA_PLAYER_LAYER);
|
||||||
|
}
|
||||||
|
|
||||||
showImagePanel();
|
showImagePanel();
|
||||||
}
|
}
|
||||||
@ -95,12 +103,13 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
List<String> mimeTypes = new ArrayList<>();
|
List<String> mimeTypes = new ArrayList<>();
|
||||||
|
|
||||||
mimeTypes.addAll(this.imagePanel.getSupportedMimeTypes());
|
mimeTypes.addAll(this.imagePanel.getSupportedMimeTypes());
|
||||||
|
if(mediaPlayerPanel != null) {
|
||||||
mimeTypes.addAll(this.mediaPlayerPanel.getSupportedMimeTypes());
|
mimeTypes.addAll(this.mediaPlayerPanel.getSupportedMimeTypes());
|
||||||
|
}
|
||||||
|
|
||||||
return mimeTypes;
|
return mimeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the view to display the given file.
|
* Set up the view to display the given file.
|
||||||
*
|
*
|
||||||
@ -120,14 +129,11 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastFile = file;
|
lastFile = file;
|
||||||
|
if (mediaPlayerPanel != null && mediaPlayerPanel.isSupported(file)) {
|
||||||
final Dimension dims = MediaFileViewer.this.getSize();
|
mediaPlayerPanel.loadFile(file);
|
||||||
//logger.info("setting node on media viewer"); //NON-NLS
|
|
||||||
if (mediaPlayerPanelInited && mediaPlayerPanel.isSupported(file)) {
|
|
||||||
mediaPlayerPanel.loadFile(file, dims);
|
|
||||||
this.showVideoPanel();
|
this.showVideoPanel();
|
||||||
} else if (imagePanelInited && imagePanel.isSupported(file)) {
|
} else if (imagePanelInited && imagePanel.isSupported(file)) {
|
||||||
imagePanel.showImageFx(file, dims);
|
imagePanel.showImageFx(file);
|
||||||
this.showImagePanel();
|
this.showImagePanel();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -158,7 +164,9 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetComponent() {
|
public void resetComponent() {
|
||||||
|
if (mediaPlayerPanel != null) {
|
||||||
mediaPlayerPanel.reset();
|
mediaPlayerPanel.reset();
|
||||||
|
}
|
||||||
imagePanel.reset();
|
imagePanel.reset();
|
||||||
lastFile = null;
|
lastFile = null;
|
||||||
}
|
}
|
||||||
|
118
Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form
Normal file → Executable file
118
Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form
Normal file → Executable file
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||||
<AuxValues>
|
<AuxValues>
|
||||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||||
@ -16,8 +16,8 @@
|
|||||||
<Layout>
|
<Layout>
|
||||||
<DimensionLayout dim="0">
|
<DimensionLayout dim="0">
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Component id="controlPanel" max="32767" attributes="0"/>
|
|
||||||
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
|
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
|
||||||
|
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
<DimensionLayout dim="1">
|
<DimensionLayout dim="1">
|
||||||
@ -41,7 +41,7 @@
|
|||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
<DimensionLayout dim="1">
|
<DimensionLayout dim="1">
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<EmptySpace min="0" pref="231" max="32767" attributes="0"/>
|
<EmptySpace min="0" pref="259" max="32767" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -51,66 +51,116 @@
|
|||||||
<Layout>
|
<Layout>
|
||||||
<DimensionLayout dim="0">
|
<DimensionLayout dim="0">
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Group type="102" alignment="0" attributes="0">
|
<Group type="102" alignment="1" attributes="0">
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Group type="102" attributes="0">
|
<Group type="102" attributes="0">
|
||||||
<EmptySpace min="6" pref="6" max="-2" attributes="0"/>
|
<Component id="playButton" min="-2" pref="64" max="-2" attributes="0"/>
|
||||||
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
|
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||||
<EmptySpace max="32767" attributes="0"/>
|
<Component id="progressSlider" pref="680" max="32767" attributes="0"/>
|
||||||
|
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||||
|
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
<Group type="102" attributes="0">
|
<Group type="102" attributes="0">
|
||||||
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
|
<Component id="infoLabel" max="32767" attributes="0"/>
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||||
<Component id="progressSlider" pref="265" max="32767" attributes="0"/>
|
<Component id="VolumeIcon" min="-2" pref="64" max="-2" attributes="0"/>
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
|
||||||
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
|
<Component id="audioSlider" min="-2" pref="229" max="-2" attributes="0"/>
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
<DimensionLayout dim="1">
|
<DimensionLayout dim="1">
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Group type="102" attributes="0">
|
<Group type="102" attributes="0">
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Component id="progressSlider" min="-2" max="-2" attributes="0"/>
|
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||||
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
|
<Component id="progressLabel" max="32767" attributes="0"/>
|
||||||
<Component id="progressLabel" alignment="0" min="-2" pref="29" max="-2" attributes="0"/>
|
<Component id="progressSlider" max="32767" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
<Component id="playButton" min="-2" max="-2" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
<EmptySpace max="32767" attributes="0"/>
|
|
||||||
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
|
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Group type="103" groupAlignment="1" attributes="0">
|
||||||
|
<Component id="audioSlider" min="-2" max="-2" attributes="0"/>
|
||||||
|
<Group type="103" groupAlignment="3" attributes="0">
|
||||||
|
<Component id="VolumeIcon" alignment="3" min="-2" pref="23" max="-2" attributes="0"/>
|
||||||
|
<Component id="infoLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
<EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
</Layout>
|
</Layout>
|
||||||
<SubComponents>
|
<SubComponents>
|
||||||
<Component class="javax.swing.JButton" name="pauseButton">
|
|
||||||
<Properties>
|
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.pauseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
|
||||||
</Property>
|
|
||||||
</Properties>
|
|
||||||
<Events>
|
|
||||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pauseButtonActionPerformed"/>
|
|
||||||
</Events>
|
|
||||||
</Component>
|
|
||||||
<Component class="javax.swing.JSlider" name="progressSlider">
|
<Component class="javax.swing.JSlider" name="progressSlider">
|
||||||
</Component>
|
|
||||||
<Component class="javax.swing.JLabel" name="progressLabel">
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
<Property name="value" type="int" value="0"/>
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||||
|
<Color id="Default Cursor"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="doubleBuffered" type="boolean" value="true"/>
|
||||||
|
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
|
<Dimension value="[36, 21]"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
|
<Dimension value="[200, 21]"/>
|
||||||
</Property>
|
</Property>
|
||||||
</Properties>
|
</Properties>
|
||||||
</Component>
|
</Component>
|
||||||
<Component class="javax.swing.JLabel" name="infoLabel">
|
<Component class="javax.swing.JLabel" name="infoLabel">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||||
|
<Color id="Default Cursor"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JButton" name="playButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="progressLabel">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="VolumeIcon">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.VolumeIcon.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JSlider" name="audioSlider">
|
||||||
|
<Properties>
|
||||||
|
<Property name="majorTickSpacing" type="int" value="10"/>
|
||||||
|
<Property name="maximum" type="int" value="50"/>
|
||||||
|
<Property name="minorTickSpacing" type="int" value="5"/>
|
||||||
|
<Property name="paintTicks" type="boolean" value="true"/>
|
||||||
|
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.audioSlider.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="value" type="int" value="25"/>
|
||||||
|
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
|
<Dimension value="[200, 21]"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
|
<Dimension value="[200, 21]"/>
|
||||||
</Property>
|
</Property>
|
||||||
</Properties>
|
</Properties>
|
||||||
</Component>
|
</Component>
|
||||||
|
863
Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java
Normal file → Executable file
863
Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java
Normal file → Executable file
@ -19,8 +19,8 @@
|
|||||||
package org.sleuthkit.autopsy.contentviewers;
|
package org.sleuthkit.autopsy.contentviewers;
|
||||||
|
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import java.awt.Dimension;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.EventQueue;
|
import java.awt.event.ActionListener;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -32,32 +32,28 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.swing.BoxLayout;
|
import javax.swing.BoxLayout;
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JSlider;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.Timer;
|
import javax.swing.Timer;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import org.freedesktop.gstreamer.Bus;
|
||||||
import org.freedesktop.gstreamer.ClockTime;
|
import org.freedesktop.gstreamer.ClockTime;
|
||||||
import org.freedesktop.gstreamer.Gst;
|
import org.freedesktop.gstreamer.Gst;
|
||||||
import org.freedesktop.gstreamer.GstException;
|
import org.freedesktop.gstreamer.GstObject;
|
||||||
import org.freedesktop.gstreamer.State;
|
import org.freedesktop.gstreamer.State;
|
||||||
import org.freedesktop.gstreamer.StateChangeReturn;
|
|
||||||
import org.freedesktop.gstreamer.elements.PlayBin;
|
import org.freedesktop.gstreamer.elements.PlayBin;
|
||||||
import org.netbeans.api.progress.ProgressHandle;
|
import org.netbeans.api.progress.ProgressHandle;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.VideoUtils;
|
import org.sleuthkit.autopsy.coreutils.VideoUtils;
|
||||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
|
||||||
import org.sleuthkit.datamodel.TskData;
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
import javafx.embed.swing.JFXPanel;
|
||||||
|
import javax.swing.event.ChangeListener;
|
||||||
|
import org.freedesktop.gstreamer.GstException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a video player that is part of the Media View layered pane. It uses
|
* This is a video player that is part of the Media View layered pane. It uses
|
||||||
@ -66,6 +62,7 @@ import org.sleuthkit.datamodel.TskData;
|
|||||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||||
public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel {
|
public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel {
|
||||||
|
|
||||||
|
//Enumerate the accepted file extensions and mimetypes
|
||||||
private static final String[] FILE_EXTENSIONS = new String[]{
|
private static final String[] FILE_EXTENSIONS = new String[]{
|
||||||
".3g2",
|
".3g2",
|
||||||
".3gp",
|
".3gp",
|
||||||
@ -94,8 +91,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
|||||||
".wav",
|
".wav",
|
||||||
".webm",
|
".webm",
|
||||||
".wma",
|
".wma",
|
||||||
".wmv",
|
".wmv",}; //NON-NLS
|
||||||
}; //NON-NLS
|
|
||||||
private static final List<String> MIME_TYPES = Arrays.asList(
|
private static final List<String> MIME_TYPES = Arrays.asList(
|
||||||
"video/3gpp",
|
"video/3gpp",
|
||||||
"video/3gpp2",
|
"video/3gpp2",
|
||||||
@ -170,462 +166,156 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
|||||||
); //NON-NLS
|
); //NON-NLS
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(MediaPlayerPanel.class.getName());
|
private static final Logger logger = Logger.getLogger(MediaPlayerPanel.class.getName());
|
||||||
private boolean gstInited;
|
private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(MediaPlayerPanel.class,
|
||||||
private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.cannotProcFile.err");
|
"GstVideoPanel.cannotProcFile.err");
|
||||||
//playback
|
|
||||||
private long durationMillis = 0;
|
//Video playback components
|
||||||
private int totalHours, totalMinutes, totalSeconds;
|
private PlayBin gstPlayBin;
|
||||||
private volatile PlayBin gstPlayBin;
|
private JavaFxAppSink fxAppSink;
|
||||||
private GstVideoRendererPanel gstVideoRenderer;
|
private JFXPanel fxPanel;
|
||||||
private final Object playbinLock = new Object(); // lock for synchronization of gstPlayBin player
|
private volatile boolean livePlayBin;
|
||||||
private AbstractFile currentFile;
|
private volatile boolean hasError;
|
||||||
|
|
||||||
|
//When a video is playing, update the UI every 75 ms
|
||||||
|
private final Timer timer = new Timer(75, new VideoPanelUpdater());
|
||||||
|
private static final int PROGRESS_SLIDER_SIZE = 2000;
|
||||||
|
|
||||||
private Timer timer;
|
|
||||||
private ExtractMedia extractMediaWorker;
|
private ExtractMedia extractMediaWorker;
|
||||||
|
|
||||||
private static final long END_TIME_MARGIN_NS = 50000000;
|
|
||||||
private static final int PLAYER_STATUS_UPDATE_INTERVAL_MS = 50;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form MediaViewVideoPanel
|
* Creates new form MediaViewVideoPanel
|
||||||
*/
|
*/
|
||||||
public MediaPlayerPanel() {
|
public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError {
|
||||||
initComponents();
|
initComponents();
|
||||||
|
initGst();
|
||||||
customizeComponents();
|
customizeComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public JButton getPauseButton() {
|
|
||||||
return pauseButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JLabel getProgressLabel() {
|
|
||||||
return progressLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSlider getProgressSlider() {
|
|
||||||
return progressSlider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JPanel getVideoPanel() {
|
|
||||||
return videoPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Has this MediaPlayerPanel been initialized correctly?
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isInited() {
|
|
||||||
return gstInited;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void customizeComponents() {
|
private void customizeComponents() {
|
||||||
if (!initGst()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
|
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
|
||||||
progressSlider.setMinimum(0);
|
progressSlider.setMinimum(0);
|
||||||
progressSlider.setMaximum(2000);
|
progressSlider.setMaximum(PROGRESS_SLIDER_SIZE);
|
||||||
progressSlider.setValue(0);
|
progressSlider.setValue(0);
|
||||||
|
|
||||||
|
//Manage the gstreamer video position when a user is dragging the slider in the panel.
|
||||||
progressSlider.addChangeListener(new ChangeListener() {
|
progressSlider.addChangeListener(new ChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void stateChanged(ChangeEvent event) {
|
public void stateChanged(ChangeEvent e) {
|
||||||
if (gstPlayBin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (progressSlider.getValueIsAdjusting()) {
|
if (progressSlider.getValueIsAdjusting()) {
|
||||||
synchronized (playbinLock) {
|
|
||||||
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
|
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
|
||||||
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE;
|
||||||
if (duration > 0) {
|
long newPos = (long) (relativePosition * duration);
|
||||||
double relativePosition = progressSlider.getValue() / 2000.0;
|
gstPlayBin.seek(newPos, TimeUnit.NANOSECONDS);
|
||||||
gstPlayBin.seek((long) (relativePosition * duration), TimeUnit.NANOSECONDS);
|
//Keep constantly updating the time label so users have a sense of
|
||||||
} else if (position > 0 || progressSlider.getValue() > 0) {
|
//where the slider they are dragging is in relation to the video time
|
||||||
gstPlayBin.seek(ClockTime.ZERO);
|
updateTimeLabel(newPos, duration);
|
||||||
progressSlider.setValue(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
private boolean initGst() {
|
//Manage the audio level when the user is adjusting the volumn slider
|
||||||
try {
|
audioSlider.addChangeListener((ChangeEvent event) -> {
|
||||||
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); //NON-NLS
|
if (audioSlider.getValueIsAdjusting()) {
|
||||||
Gst.init();
|
int audioPercent = audioSlider.getValue() * 2;
|
||||||
gstInited = true;
|
gstPlayBin.setVolumePercent(audioPercent);
|
||||||
} catch (GstException | UnsatisfiedLinkError ex) {
|
|
||||||
gstInited = false;
|
|
||||||
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", ex); //NON-NLS
|
|
||||||
MessageNotifyUtil.Notify.error(
|
|
||||||
NbBundle.getMessage(this.getClass(), "GstVideoPanel.initGst.gstException.msg"),
|
|
||||||
ex.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize all the necessary variables to play an audio/video file.
|
|
||||||
*
|
|
||||||
* @param file Media file to play.
|
|
||||||
* @param dims Dimension of the parent window.
|
|
||||||
*/
|
|
||||||
@NbBundle.Messages ({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
|
|
||||||
void loadFile(final AbstractFile file, final Dimension dims) {
|
|
||||||
EventQueue.invokeLater(() -> {
|
|
||||||
reset();
|
|
||||||
infoLabel.setText("");
|
|
||||||
currentFile = file;
|
|
||||||
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
|
|
||||||
if (deleted) {
|
|
||||||
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
|
|
||||||
videoPanel.removeAll();
|
|
||||||
pauseButton.setEnabled(false);
|
|
||||||
progressSlider.setEnabled(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
java.io.File ioFile;
|
|
||||||
try {
|
|
||||||
ioFile = VideoUtils.getVideoFileInTempDir(file);
|
|
||||||
} catch (NoCurrentCaseException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
|
|
||||||
infoLabel.setText(Bundle.GstVideoPanel_noOpenCase_errMsg());
|
|
||||||
pauseButton.setEnabled(false);
|
|
||||||
progressSlider.setEnabled(false);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String path = "";
|
|
||||||
try {
|
|
||||||
path = file.getUniquePath();
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Cannot get unique path of video file.", ex); //NON-NLS
|
|
||||||
}
|
|
||||||
infoLabel.setText(path);
|
|
||||||
infoLabel.setToolTipText(path);
|
|
||||||
pauseButton.setEnabled(true);
|
|
||||||
progressSlider.setEnabled(true);
|
|
||||||
timer = new Timer(PLAYER_STATUS_UPDATE_INTERVAL_MS, event -> {
|
|
||||||
if (!progressSlider.getValueIsAdjusting()) {
|
|
||||||
long duration;
|
|
||||||
long position;
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
|
|
||||||
position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
|
||||||
if (duration > 0) {
|
|
||||||
long positionDelta = duration - position;
|
|
||||||
if (positionDelta <= END_TIME_MARGIN_NS && gstPlayBin.isPlaying()) {
|
|
||||||
gstPlayBin.pause();
|
|
||||||
if (gstPlayBin.seek(ClockTime.ZERO) == false) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin.seek() failed."); //NON-NLS
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
progressSlider.setValue(0);
|
|
||||||
pauseButton.setText("►");
|
|
||||||
} else {
|
|
||||||
double relativePosition = (double) position / duration;
|
|
||||||
progressSlider.setValue((int) (relativePosition * 2000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
durationMillis = duration / 1000000;
|
|
||||||
// pick out the total hours, minutes, seconds
|
|
||||||
long durationSeconds = (int) durationMillis / 1000;
|
|
||||||
totalHours = (int) durationSeconds / 3600;
|
|
||||||
durationSeconds -= totalHours * 3600;
|
|
||||||
totalMinutes = (int) durationSeconds / 60;
|
|
||||||
durationSeconds -= totalMinutes * 60;
|
|
||||||
totalSeconds = (int) durationSeconds;
|
|
||||||
|
|
||||||
long millisElapsed = position / 1000000;
|
|
||||||
// pick out the elapsed hours, minutes, seconds
|
|
||||||
long secondsElapsed = millisElapsed / 1000;
|
|
||||||
int elapsedHours = (int) secondsElapsed / 3600;
|
|
||||||
secondsElapsed -= elapsedHours * 3600;
|
|
||||||
int elapsedMinutes = (int) secondsElapsed / 60;
|
|
||||||
secondsElapsed -= elapsedMinutes * 60;
|
|
||||||
int elapsedSeconds = (int) secondsElapsed;
|
|
||||||
|
|
||||||
String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
|
|
||||||
String durationStr = String.format(durationFormat,
|
|
||||||
elapsedHours, elapsedMinutes, elapsedSeconds,
|
|
||||||
totalHours, totalMinutes, totalSeconds);
|
|
||||||
progressLabel.setText(durationStr);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
timer.start();
|
|
||||||
|
|
||||||
gstVideoRenderer = new GstVideoRendererPanel();
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
if (gstPlayBin != null) {
|
|
||||||
gstPlayBin.dispose();
|
|
||||||
}
|
|
||||||
gstPlayBin = new PlayBin("VideoPlayer"); //NON-NLS
|
|
||||||
gstPlayBin.setVideoSink(gstVideoRenderer.getVideoSink());
|
|
||||||
|
|
||||||
videoPanel.removeAll();
|
|
||||||
|
|
||||||
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
|
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
|
||||||
|
fxPanel = new JFXPanel();
|
||||||
videoPanel.add(gstVideoRenderer);//add jfx ui to JPanel
|
videoPanel.add(fxPanel);//add jfx ui to JPanel
|
||||||
|
|
||||||
videoPanel.setVisible(true);
|
|
||||||
|
|
||||||
gstPlayBin.setInputFile(ioFile);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
private void initGst() throws GstException, UnsatisfiedLinkError {
|
||||||
|
logger.log(Level.INFO, "Attempting initializing of gstreamer for video/audio viewing"); //NON-NLS
|
||||||
|
Gst.init();
|
||||||
|
gstPlayBin = new PlayBin("VideoPlayer");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare this MediaViewVideoPanel to accept a different media file.
|
* Loads the file by spawning off a background task to handle file copying
|
||||||
|
* and video component initializations.
|
||||||
|
*
|
||||||
|
* @param file Media file to play.
|
||||||
|
*/
|
||||||
|
@NbBundle.Messages({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
|
||||||
|
void loadFile(final AbstractFile file) {
|
||||||
|
//Ensure everything is back in the initial state
|
||||||
|
reset();
|
||||||
|
|
||||||
|
infoLabel.setText("");
|
||||||
|
if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
|
||||||
|
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Pushing off initialization to the background
|
||||||
|
extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
|
||||||
|
extractMediaWorker.execute();
|
||||||
|
} catch (NoCurrentCaseException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
|
||||||
|
infoLabel.setText(String.format("<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
|
||||||
|
enableComponents(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assume no support on a fresh reset until we begin loading the file
|
||||||
|
* for play.
|
||||||
|
*/
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"MediaPlayerPanel.noSupport=File not supported."
|
||||||
|
})
|
||||||
|
void resetComponents() {
|
||||||
|
progressLabel.setText(String.format("%s/%s", Bundle.MediaPlayerPanel_unknownTime(),
|
||||||
|
Bundle.MediaPlayerPanel_unknownTime()));
|
||||||
|
infoLabel.setText(Bundle.MediaPlayerPanel_noSupport());
|
||||||
|
progressSlider.setValue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return this panel to its initial state.
|
||||||
*/
|
*/
|
||||||
void reset() {
|
void reset() {
|
||||||
if (timer != null) {
|
|
||||||
timer.stop();
|
timer.stop();
|
||||||
|
if(livePlayBin && !hasError) {
|
||||||
|
gstPlayBin.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset the progress label text on the event dispatch thread
|
hasError = false;
|
||||||
SwingUtilities.invokeLater(() -> {
|
livePlayBin = false;
|
||||||
progressLabel.setText("");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isInited()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
if (gstPlayBin != null) {
|
|
||||||
if (gstPlayBin.isPlaying() && gstPlayBin.stop() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin.stop() failed."); //NON-NLS
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gstPlayBin.dispose();
|
gstPlayBin.dispose();
|
||||||
gstPlayBin = null;
|
|
||||||
}
|
if (fxAppSink != null) {
|
||||||
gstVideoRenderer = null;
|
fxAppSink.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
progressSlider.setValue(0);
|
videoPanel.removeAll();
|
||||||
pauseButton.setText("►");
|
|
||||||
|
|
||||||
currentFile = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* regenerated by the Form Editor.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">
|
|
||||||
private void initComponents() {
|
|
||||||
|
|
||||||
videoPanel = new javax.swing.JPanel();
|
|
||||||
controlPanel = new javax.swing.JPanel();
|
|
||||||
pauseButton = new javax.swing.JButton();
|
|
||||||
progressSlider = new javax.swing.JSlider();
|
|
||||||
progressLabel = new javax.swing.JLabel();
|
|
||||||
infoLabel = new javax.swing.JLabel();
|
|
||||||
|
|
||||||
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.setVerticalGroup(
|
|
||||||
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
||||||
.addGap(0, 231, Short.MAX_VALUE)
|
|
||||||
);
|
|
||||||
|
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
|
|
||||||
pauseButton.addActionListener(new java.awt.event.ActionListener() {
|
|
||||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
|
||||||
pauseButtonActionPerformed(evt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
|
|
||||||
|
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N
|
|
||||||
|
|
||||||
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.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())
|
|
||||||
);
|
|
||||||
|
|
||||||
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.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))
|
|
||||||
);
|
|
||||||
}// </editor-fold>
|
|
||||||
|
|
||||||
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
|
|
||||||
synchronized (playbinLock) {
|
|
||||||
if (gstPlayBin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
State state = gstPlayBin.getState();
|
|
||||||
if (state.equals(State.PLAYING)) {
|
|
||||||
if (gstPlayBin.pause() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin.pause() failed."); //NON-NLS
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pauseButton.setText("►");
|
|
||||||
} else if (state.equals(State.PAUSED)) {
|
|
||||||
if (gstPlayBin.play() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin.play() failed."); //NON-NLS
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pauseButton.setText("||");
|
|
||||||
} else if (state.equals(State.READY) || state.equals(State.NULL)) {
|
|
||||||
final File tempVideoFile;
|
|
||||||
try {
|
|
||||||
tempVideoFile = VideoUtils.getVideoFileInTempDir(currentFile);
|
|
||||||
} catch (NoCurrentCaseException ex) {
|
|
||||||
logger.log(Level.WARNING, "Exception while getting open case."); //NON-NLS
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extractMediaWorker != null) {
|
if (extractMediaWorker != null) {
|
||||||
extractMediaWorker.cancel(true);
|
extractMediaWorker.cancel(true);
|
||||||
extractMediaWorker = null;
|
|
||||||
}
|
}
|
||||||
extractMediaWorker = new ExtractMedia(currentFile, tempVideoFile);
|
|
||||||
extractMediaWorker.execute();
|
|
||||||
|
|
||||||
|
resetComponents();
|
||||||
|
enableComponents(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}//GEN-LAST:event_pauseButtonActionPerformed
|
|
||||||
|
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
|
||||||
private javax.swing.JPanel controlPanel;
|
|
||||||
private javax.swing.JLabel infoLabel;
|
|
||||||
private javax.swing.JButton pauseButton;
|
|
||||||
private javax.swing.JLabel progressLabel;
|
|
||||||
private javax.swing.JSlider progressSlider;
|
|
||||||
private javax.swing.JPanel videoPanel;
|
|
||||||
// End of variables declaration//GEN-END:variables
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thread that extracts and plays a file
|
* If the node has been reset but messages from the previous PlayBin are
|
||||||
|
* still firing, ignore them.
|
||||||
*/
|
*/
|
||||||
private class ExtractMedia extends SwingWorker<Long, Void> {
|
synchronized void setLabelText(String msg) {
|
||||||
|
if (livePlayBin) {
|
||||||
private ProgressHandle progress;
|
infoLabel.setText(msg);
|
||||||
private final AbstractFile sourceFile;
|
|
||||||
private final java.io.File tempFile;
|
|
||||||
|
|
||||||
ExtractMedia(AbstractFile sFile, java.io.File jFile) {
|
|
||||||
this.sourceFile = sFile;
|
|
||||||
this.tempFile = jFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Long doInBackground() throws Exception {
|
|
||||||
if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
|
|
||||||
progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
|
|
||||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
|
|
||||||
progress.start(100);
|
|
||||||
try {
|
|
||||||
Files.createParentDirs(tempFile);
|
|
||||||
return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* clean up or start the worker threads
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
try {
|
|
||||||
super.get(); //block and get all exceptions thrown while doInBackground()
|
|
||||||
} catch (CancellationException ex) {
|
|
||||||
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
|
|
||||||
} catch (ExecutionException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
|
|
||||||
} finally {
|
|
||||||
if (progress != null) {
|
|
||||||
progress.finish();
|
|
||||||
}
|
|
||||||
if (!this.isCancelled()) {
|
|
||||||
playMedia();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void playMedia() {
|
private void enableComponents(boolean isEnabled) {
|
||||||
if (tempFile == null || !tempFile.exists()) {
|
playButton.setEnabled(isEnabled);
|
||||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
|
progressSlider.setEnabled(isEnabled);
|
||||||
return;
|
videoPanel.setEnabled(isEnabled);
|
||||||
}
|
audioSlider.setEnabled(isEnabled);
|
||||||
synchronized (playbinLock) {
|
|
||||||
gstPlayBin.seek(ClockTime.ZERO);
|
|
||||||
// must play, then pause and get state to get duration.
|
|
||||||
if (gstPlayBin.play() == StateChangeReturn.FAILURE) {
|
|
||||||
logger.log(Level.WARNING, "Attempt to call PlayBin.play() failed."); //NON-NLS
|
|
||||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pauseButton.setText("||");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -674,4 +364,339 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats current time and total time as the following ratio: HH:MM:SS /
|
||||||
|
* HH:MM:SS
|
||||||
|
*
|
||||||
|
* @param posNs
|
||||||
|
* @param totalNs
|
||||||
|
*/
|
||||||
|
private void updateTimeLabel(long start, long total) {
|
||||||
|
progressLabel.setText(formatTime(start, false) + "/" + formatTime(total, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert nanoseconds into an HH:MM:SS format.
|
||||||
|
*/
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"MediaPlayerPanel.unknownTime=Unknown",
|
||||||
|
"MediaPlayerPanel.timeFormat=%02d:%02d:%02d"
|
||||||
|
})
|
||||||
|
private String formatTime(long ns, boolean ceiling) {
|
||||||
|
if (ns == -1) {
|
||||||
|
return Bundle.MediaPlayerPanel_unknownTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
double millis = ns / 1000000.0;
|
||||||
|
double seconds;
|
||||||
|
if (ceiling) {
|
||||||
|
seconds = Math.ceil(millis / 1000);
|
||||||
|
} else {
|
||||||
|
seconds = millis / 1000;
|
||||||
|
}
|
||||||
|
double hours = seconds / 3600;
|
||||||
|
seconds -= (int) hours * 3600;
|
||||||
|
double minutes = seconds / 60;
|
||||||
|
seconds -= (int) minutes * 60;
|
||||||
|
|
||||||
|
return String.format(Bundle.MediaPlayerPanel_timeFormat(), (int) hours, (int) minutes, (int) seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread that extracts a file and initializes all of the playback
|
||||||
|
* components.
|
||||||
|
*/
|
||||||
|
private class ExtractMedia extends SwingWorker<Void, Void> {
|
||||||
|
|
||||||
|
private ProgressHandle progress;
|
||||||
|
private final AbstractFile sourceFile;
|
||||||
|
private final java.io.File tempFile;
|
||||||
|
|
||||||
|
ExtractMedia(AbstractFile sFile, File jFile) {
|
||||||
|
this.sourceFile = sFile;
|
||||||
|
this.tempFile = jFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Exception {
|
||||||
|
if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) {
|
||||||
|
progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
|
||||||
|
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
|
||||||
|
progress.start(100);
|
||||||
|
try {
|
||||||
|
Files.createParentDirs(tempFile);
|
||||||
|
ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.WARNING, "Error creating parent directory for copying video/audio in temp directory", ex); //NON-NLS
|
||||||
|
} finally {
|
||||||
|
progress.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the playback components if the extraction was successful.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
super.get();
|
||||||
|
|
||||||
|
//Video is ready for playback. Clean up previous components and create new ones
|
||||||
|
gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI());
|
||||||
|
//Create a custom AppSink that hooks into JavaFx panels for video display
|
||||||
|
fxPanel = new JFXPanel();
|
||||||
|
fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel);
|
||||||
|
gstPlayBin.setVideoSink(fxAppSink);
|
||||||
|
|
||||||
|
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
|
||||||
|
videoPanel.add(fxPanel);//add jfx ui to JPanel
|
||||||
|
|
||||||
|
//Configure event handling
|
||||||
|
attachEOSListener(gstPlayBin); //Handle end of video events
|
||||||
|
attachStateListener(gstPlayBin); //Handle syncing play/pause button to the stream state
|
||||||
|
attachErrorListener(gstPlayBin); //Handle errors gracefully when they are encountered
|
||||||
|
|
||||||
|
//Customize components
|
||||||
|
gstPlayBin.setVolumePercent(audioSlider.getValue() * 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the PlayBin for playback.
|
||||||
|
*/
|
||||||
|
gstPlayBin.ready();
|
||||||
|
livePlayBin = true;
|
||||||
|
//Customize components
|
||||||
|
enableComponents(true);
|
||||||
|
} catch (CancellationException ex) {
|
||||||
|
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for the end of stream event, in which case we conveniently
|
||||||
|
* reset the video for the user.
|
||||||
|
*/
|
||||||
|
private void attachEOSListener(PlayBin gstPlayBin) {
|
||||||
|
gstPlayBin.getBus().connect(new Bus.EOS() {
|
||||||
|
@Override
|
||||||
|
public void endOfStream(GstObject go) {
|
||||||
|
gstPlayBin.seek(ClockTime.ZERO);
|
||||||
|
progressSlider.setValue(0);
|
||||||
|
/**
|
||||||
|
* Keep the video from automatically playing
|
||||||
|
*/
|
||||||
|
Gst.getExecutorService().submit(() -> gstPlayBin.pause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for state changes and update the play/pause button
|
||||||
|
* accordingly. In addition, handle the state transition from
|
||||||
|
* READY -> PAUSED.
|
||||||
|
*/
|
||||||
|
private void attachStateListener(PlayBin gstPlayBin) {
|
||||||
|
gstPlayBin.getBus().connect(new Bus.STATE_CHANGED() {
|
||||||
|
@Override
|
||||||
|
public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
|
||||||
|
/**
|
||||||
|
* If we are ready, it is safe to transition to the pause state
|
||||||
|
* to initiate data-flow for pre-roll frame and duration
|
||||||
|
* information.
|
||||||
|
*/
|
||||||
|
if (State.READY.equals(currentState)) {
|
||||||
|
Gst.getExecutorService().submit(() -> gstPlayBin.pause());
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (State.PLAYING.equals(currentState)) {
|
||||||
|
playButton.setText("||");
|
||||||
|
} else {
|
||||||
|
playButton.setText("►");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On error messages disable the UI and show the user an error was
|
||||||
|
* encountered.
|
||||||
|
*/
|
||||||
|
private void attachErrorListener(PlayBin gstPlayBin) {
|
||||||
|
gstPlayBin.getBus().connect(new Bus.ERROR() {
|
||||||
|
@Override
|
||||||
|
public void errorMessage(GstObject go, int i, String string) {
|
||||||
|
enableComponents(false);
|
||||||
|
setLabelText(String.format("<html><font color='red'>%s</font></html>",
|
||||||
|
MEDIA_PLAYER_ERROR_STRING));
|
||||||
|
timer.stop();
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the video time bar and the time label when a video is playing.
|
||||||
|
*/
|
||||||
|
private class VideoPanelUpdater implements ActionListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (!progressSlider.getValueIsAdjusting()) {
|
||||||
|
if(livePlayBin) {
|
||||||
|
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
||||||
|
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
|
||||||
|
/**
|
||||||
|
* Duration may not be known until there is video data in the
|
||||||
|
* pipeline. We start this updater when data-flow has just been
|
||||||
|
* initiated so buffering may still be in progress.
|
||||||
|
*/
|
||||||
|
if (duration != -1) {
|
||||||
|
double relativePosition = (double) position / duration;
|
||||||
|
progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimeLabel(position, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* regenerated by the Form Editor.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||||
|
private void initComponents() {
|
||||||
|
|
||||||
|
videoPanel = new javax.swing.JPanel();
|
||||||
|
controlPanel = new javax.swing.JPanel();
|
||||||
|
progressSlider = new javax.swing.JSlider();
|
||||||
|
infoLabel = new javax.swing.JLabel();
|
||||||
|
playButton = new javax.swing.JButton();
|
||||||
|
progressLabel = new javax.swing.JLabel();
|
||||||
|
VolumeIcon = new javax.swing.JLabel();
|
||||||
|
audioSlider = new javax.swing.JSlider();
|
||||||
|
|
||||||
|
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.setVerticalGroup(
|
||||||
|
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGap(0, 259, Short.MAX_VALUE)
|
||||||
|
);
|
||||||
|
|
||||||
|
progressSlider.setValue(0);
|
||||||
|
progressSlider.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
|
||||||
|
progressSlider.setDoubleBuffered(true);
|
||||||
|
progressSlider.setMinimumSize(new java.awt.Dimension(36, 21));
|
||||||
|
progressSlider.setPreferredSize(new java.awt.Dimension(200, 21));
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N
|
||||||
|
infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N
|
||||||
|
playButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
playButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N
|
||||||
|
|
||||||
|
audioSlider.setMajorTickSpacing(10);
|
||||||
|
audioSlider.setMaximum(50);
|
||||||
|
audioSlider.setMinorTickSpacing(5);
|
||||||
|
audioSlider.setPaintTicks(true);
|
||||||
|
audioSlider.setToolTipText(org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.audioSlider.toolTipText")); // NOI18N
|
||||||
|
audioSlider.setValue(25);
|
||||||
|
audioSlider.setMinimumSize(new java.awt.Dimension(200, 21));
|
||||||
|
audioSlider.setPreferredSize(new java.awt.Dimension(200, 21));
|
||||||
|
|
||||||
|
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
|
||||||
|
controlPanel.setLayout(controlPanelLayout);
|
||||||
|
controlPanelLayout.setHorizontalGroup(
|
||||||
|
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
|
||||||
|
.addContainerGap()
|
||||||
|
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||||
|
.addComponent(playButton, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||||
|
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 680, Short.MAX_VALUE)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||||
|
.addComponent(progressLabel))
|
||||||
|
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||||
|
.addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
.addGap(18, 18, 18)
|
||||||
|
.addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addGap(2, 2, 2)
|
||||||
|
.addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, 229, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||||
|
.addContainerGap())
|
||||||
|
);
|
||||||
|
controlPanelLayout.setVerticalGroup(
|
||||||
|
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||||
|
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||||
|
.addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||||
|
.addComponent(playButton))
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||||
|
.addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||||
|
.addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addComponent(infoLabel)))
|
||||||
|
.addGap(13, 13, 13))
|
||||||
|
);
|
||||||
|
|
||||||
|
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||||
|
this.setLayout(layout);
|
||||||
|
layout.setHorizontalGroup(
|
||||||
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
.addComponent(controlPanel, 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))
|
||||||
|
);
|
||||||
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
|
private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed
|
||||||
|
if(gstPlayBin.isPlaying()) {
|
||||||
|
gstPlayBin.pause();
|
||||||
|
} else {
|
||||||
|
gstPlayBin.play();
|
||||||
|
}
|
||||||
|
}//GEN-LAST:event_playButtonActionPerformed
|
||||||
|
|
||||||
|
|
||||||
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
private javax.swing.JLabel VolumeIcon;
|
||||||
|
private javax.swing.JSlider audioSlider;
|
||||||
|
private javax.swing.JPanel controlPanel;
|
||||||
|
private javax.swing.JLabel infoLabel;
|
||||||
|
private javax.swing.JButton playButton;
|
||||||
|
private javax.swing.JLabel progressLabel;
|
||||||
|
private javax.swing.JSlider progressSlider;
|
||||||
|
private javax.swing.JPanel videoPanel;
|
||||||
|
// End of variables declaration//GEN-END:variables
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.contentviewers;
|
package org.sleuthkit.autopsy.contentviewers;
|
||||||
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -172,9 +171,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
|||||||
* Show the contents of the given AbstractFile as a visual image.
|
* Show the contents of the given AbstractFile as a visual image.
|
||||||
*
|
*
|
||||||
* @param file image file to show
|
* @param file image file to show
|
||||||
* @param dims dimension of the parent window (ignored)
|
|
||||||
*/
|
*/
|
||||||
void showImageFx(final AbstractFile file, final Dimension dims) {
|
void showImageFx(final AbstractFile file) {
|
||||||
if (!fxInited) {
|
if (!fxInited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -230,6 +230,20 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up tracking to support any custom artifact or attribute types
|
||||||
|
for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
|
||||||
|
oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
|
||||||
|
}
|
||||||
|
for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
|
||||||
|
try {
|
||||||
|
oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
handleError("Error looking up attribute name " + type.getLabel(),
|
||||||
|
Bundle.CreatePortableCaseModule_generateReport_errorLookingUpAttrType(type.getLabel()),
|
||||||
|
ex, progressPanel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the tagged files
|
// Copy the tagged files
|
||||||
try {
|
try {
|
||||||
for(TagName tagName:tagNames) {
|
for(TagName tagName:tagNames) {
|
||||||
@ -245,20 +259,6 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up tracking to support any custom artifact or attribute types
|
|
||||||
for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
|
|
||||||
oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
|
|
||||||
}
|
|
||||||
for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
|
|
||||||
try {
|
|
||||||
oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
handleError("Error looking up attribute name " + type.getLabel(),
|
|
||||||
Bundle.CreatePortableCaseModule_generateReport_errorLookingUpAttrType(type.getLabel()),
|
|
||||||
ex, progressPanel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the tagged artifacts and associated files
|
// Copy the tagged artifacts and associated files
|
||||||
try {
|
try {
|
||||||
for(TagName tagName:tagNames) {
|
for(TagName tagName:tagNames) {
|
||||||
@ -577,17 +577,7 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
|||||||
})
|
})
|
||||||
private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
|
private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
|
||||||
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
|
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
|
||||||
|
return copyContent(content);
|
||||||
long newFileId;
|
|
||||||
CaseDbTransaction trans = portableSkCase.beginTransaction();
|
|
||||||
try {
|
|
||||||
newFileId = copyContent(content, trans);
|
|
||||||
trans.commit();
|
|
||||||
return newFileId;
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
trans.rollback();
|
|
||||||
throw(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -600,7 +590,7 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
|||||||
*
|
*
|
||||||
* @throws TskCoreException
|
* @throws TskCoreException
|
||||||
*/
|
*/
|
||||||
private long copyContent(Content content, CaseDbTransaction trans) throws TskCoreException {
|
private long copyContent(Content content) throws TskCoreException {
|
||||||
|
|
||||||
// Check if we've already copied this content
|
// Check if we've already copied this content
|
||||||
if (oldIdToNewContent.containsKey(content.getId())) {
|
if (oldIdToNewContent.containsKey(content.getId())) {
|
||||||
@ -612,10 +602,16 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
|||||||
// - Copy this content
|
// - Copy this content
|
||||||
long parentId = 0;
|
long parentId = 0;
|
||||||
if (content.getParent() != null) {
|
if (content.getParent() != null) {
|
||||||
parentId = copyContent(content.getParent(), trans);
|
parentId = copyContent(content.getParent());
|
||||||
}
|
}
|
||||||
|
|
||||||
Content newContent;
|
Content newContent;
|
||||||
|
if (content instanceof BlackboardArtifact) {
|
||||||
|
BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
|
||||||
|
newContent = copyArtifact(parentId, artifactToCopy);
|
||||||
|
} else {
|
||||||
|
CaseDbTransaction trans = portableSkCase.beginTransaction();
|
||||||
|
try {
|
||||||
if (content instanceof Image) {
|
if (content instanceof Image) {
|
||||||
Image image = (Image)content;
|
Image image = (Image)content;
|
||||||
newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
|
newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
|
||||||
@ -632,6 +628,9 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
|||||||
newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
|
newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
|
||||||
fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
|
fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
|
||||||
fs.getName(), trans);
|
fs.getName(), trans);
|
||||||
|
} else if (content instanceof BlackboardArtifact) {
|
||||||
|
BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
|
||||||
|
newContent = copyArtifact(parentId, artifactToCopy);
|
||||||
} else if (content instanceof AbstractFile) {
|
} else if (content instanceof AbstractFile) {
|
||||||
AbstractFile abstractFile = (AbstractFile)content;
|
AbstractFile abstractFile = (AbstractFile)content;
|
||||||
|
|
||||||
@ -674,6 +673,12 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
|||||||
} else {
|
} else {
|
||||||
throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName());
|
throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName());
|
||||||
}
|
}
|
||||||
|
trans.commit();
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
trans.rollback();
|
||||||
|
throw(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save the new object
|
// Save the new object
|
||||||
oldIdToNewContent.put(content.getId(), newContent);
|
oldIdToNewContent.put(content.getId(), newContent);
|
||||||
|
@ -514,7 +514,7 @@ public final class DrawableDB {
|
|||||||
private boolean initializeDBSchema() {
|
private boolean initializeDBSchema() {
|
||||||
dbWriteLock();
|
dbWriteLock();
|
||||||
try {
|
try {
|
||||||
boolean existingDB = true;
|
boolean drawableDbTablesExist = true;
|
||||||
|
|
||||||
if (isClosed()) {
|
if (isClosed()) {
|
||||||
logger.log(Level.SEVERE, "The drawables database is closed"); //NON-NLS
|
logger.log(Level.SEVERE, "The drawables database is closed"); //NON-NLS
|
||||||
@ -533,11 +533,11 @@ public final class DrawableDB {
|
|||||||
*/
|
*/
|
||||||
try (Statement stmt = con.createStatement()) {
|
try (Statement stmt = con.createStatement()) {
|
||||||
|
|
||||||
// Check if the database is a new or existing database
|
// Check if the database is new or an existing database
|
||||||
existingDB = doesTableExist("datasources");
|
drawableDbTablesExist = doesTableExist("drawable_files");
|
||||||
if (false == doesTableExist(IG_DB_INFO_TABLE)) {
|
if (false == doesTableExist(IG_DB_INFO_TABLE)) {
|
||||||
try {
|
try {
|
||||||
VersionNumber ig_creation_schema_version = existingDB
|
VersionNumber ig_creation_schema_version = drawableDbTablesExist
|
||||||
? IG_STARTING_SCHEMA_VERSION
|
? IG_STARTING_SCHEMA_VERSION
|
||||||
: IG_SCHEMA_VERSION;
|
: IG_SCHEMA_VERSION;
|
||||||
|
|
||||||
@ -651,7 +651,8 @@ public final class DrawableDB {
|
|||||||
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
|
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
VersionNumber ig_creation_schema_version = existingDB
|
boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME);
|
||||||
|
VersionNumber ig_creation_schema_version = caseDbTablesExist
|
||||||
? IG_STARTING_SCHEMA_VERSION
|
? IG_STARTING_SCHEMA_VERSION
|
||||||
: IG_SCHEMA_VERSION;
|
: IG_SCHEMA_VERSION;
|
||||||
|
|
||||||
@ -935,13 +936,14 @@ public final class DrawableDB {
|
|||||||
*/
|
*/
|
||||||
private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction ) throws TskCoreException {
|
private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction ) throws TskCoreException {
|
||||||
|
|
||||||
if (currVersion.getMajor() != 1 ||
|
// Upgrade if current version is 1.0
|
||||||
currVersion.getMinor() != 0) {
|
// or 1.1 - a bug in versioning alllowed some databases to be versioned as 1.1 without the actual corresponding upgrade. This allows such databases to be fixed, if needed.
|
||||||
|
if (!(currVersion.getMajor() == 1 &&
|
||||||
|
(currVersion.getMinor() == 0 || currVersion.getMinor() == 1))) {
|
||||||
return currVersion;
|
return currVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.0 -> 1.1 upgrade
|
// Add a 'is_analyzed' column to groups table in CaseDB
|
||||||
// Add a 'isAnalyzed' column to groups table in CaseDB
|
|
||||||
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
|
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
|
||||||
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction )) {
|
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction )) {
|
||||||
tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
|
tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user