mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Revert "4589 gst video panel"
This commit is contained in:
parent
2818c7d029
commit
0219607f8a
14
BUILDING.txt
14
BUILDING.txt
@ -1,4 +1,4 @@
|
||||
Last Updated: 1 February 2019
|
||||
Last Updated: 30 October 2015
|
||||
|
||||
This file outlines what it takes to build Autopsy from source.
|
||||
|
||||
@ -55,13 +55,11 @@ from the TSK root directory to install the libraries and such in
|
||||
the needed places (i.e. '/usr/local').
|
||||
|
||||
|
||||
3) For Windows builds, GStreamer must be setup. GStreamer is used to view video
|
||||
files. You can either download it and install it, or you can copy it from the
|
||||
'thirdparty/gstreamer' folder. Both 32-bit (x86) and 64-bit (x86_64) versions
|
||||
are included, so feel free to exclude what you want. You will need to unzip the
|
||||
'lib/gstreamer-1.0/libgstlibav.zip' file into it's residing
|
||||
folder. You will also need the 'bin' and 'lib/gstreamer-1.0' paths included in
|
||||
your Windows PATH environment variable.
|
||||
3) For 32-bit targets, get GStreamer Setup. GStreamer is used to view video files.
|
||||
You can either download it and install it or manually by unzipping the
|
||||
version that is included in the 'thirdparty/gstreamer' folder. You
|
||||
will need the 'bin' and 'lib/gstreamer-0.10' folders to be in your
|
||||
Windows PATH environment variable.
|
||||
|
||||
NOTE: This has not been fully tested in non-Windows environments
|
||||
yet, so we don't have instructions for that yet.
|
||||
|
@ -76,7 +76,6 @@ file.reference.xz-1.6.jar=release/modules/ext/xz-1.6.jar
|
||||
file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar
|
||||
file.reference.SparseBitSet-1.1.jar=release/modules/ext/SparseBitSet-1.1.jar
|
||||
file.reference.commons-validator-1.6.jar=release/modules/ext/commons-validator-1.6.jar
|
||||
file.reference.jna-3.4.0.jar=release/modules/ext/jna-3.4.0.jar
|
||||
javac.source=1.8
|
||||
javac.compilerargs=-Xlint -Xlint:-serial
|
||||
license.file=../LICENSE-2.0.txt
|
||||
|
@ -9,6 +9,7 @@ FXVideoPanel.progressLabel.buffering=Buffering...
|
||||
FXVideoPanel.media.unsupportedFormat=Unsupported Format.
|
||||
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.initGst.otherException.msg=Error initializing gstreamer for audio/video viewing 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.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.
|
||||
|
@ -9,6 +9,7 @@ FXVideoPanel.progressLabel.buffering=\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0\
|
||||
FXVideoPanel.media.unsupportedFormat=\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u306a\u3044\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u3059\u3002
|
||||
GstVideoPanel.cannotProcFile.err=\u30e1\u30c7\u30a4\u30a2\u30d7\u30ec\u30fc\u30e4\u30fc\u3067\u306f\u3053\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u51e6\u7406\u3067\u304d\u307e\u305b\u3093\u3002
|
||||
GstVideoPanel.initGst.gstException.msg=\u30aa\u30fc\u30c7\u30a3\u30aa\uff0f\u30d3\u30c7\u30aa\u306e\u518d\u751f\u304a\u3088\u3073\u30d5\u30ec\u30fc\u30e0\u306e\u62bd\u51fa\u306b\u4f7f\u7528\u3059\u308bGStreamer\u306e\u521d\u671f\u5316\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30d3\u30c7\u30aa\u304a\u3088\u3073\u30aa\u30fc\u30c7\u30a3\u30aa\u518d\u751f\u304c\u7121\u52b9\u5316\u3055\u308c\u307e\u3059\u3002
|
||||
GstVideoPanel.initGst.otherException.msg=\u30aa\u30fc\u30c7\u30a3\u30aa\uff0f\u30d3\u30c7\u30aa\u306e\u518d\u751f\u304a\u3088\u3073\u30d5\u30ec\u30fc\u30e0\u306e\u62bd\u51fa\u306b\u4f7f\u7528\u3059\u308bGStreamer\u306e\u521d\u671f\u5316\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30d3\u30c7\u30aa\u304a\u3088\u3073\u30aa\u30fc\u30c7\u30a3\u30aa\u518d\u751f\u304c\u7121\u52b9\u5316\u3055\u308c\u307e\u3059\u3002
|
||||
GstVideoPanel.setupVideo.infoLabel.text=\u524a\u9664\u3055\u308c\u305f\u30d3\u30c7\u30aa\u306e\u518d\u751f\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u5916\u90e8\u30d7\u30ec\u30fc\u30e4\u30fc\u3092\u4f7f\u7528\u3057\u3066\u4e0b\u3055\u3044\u3002
|
||||
GstVideoPanel.exception.problemFile.msg=\u30d5\u30a1\u30a4\u30eb({0})\u304b\u3089\u30d5\u30ec\u30fc\u30e0\u3092\u62bd\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
|
||||
GstVideoPanel.exception.problemPlay.msg=\u30d3\u30c7\u30aa\u30d5\u30a1\u30a4\u30eb\u306b\u554f\u984c\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u9577\u3055\u3092\u78ba\u8a8d\u4e2d\u306b\u518d\u751f\u3092\u3057\u3088\u3046\u3068\u3057\u305f\u969b\u306b\u554f\u984c\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
|
||||
|
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.4" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="0" green="0" red="0" type="rgb"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jFXPanel" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jFXPanel" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javafx.embed.swing.JFXPanel" name="jFXPanel">
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="400" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="300" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
669
Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java
Normal file
669
Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java
Normal file
@ -0,0 +1,669 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 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 com.google.common.io.Files;
|
||||
import java.awt.Dimension;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.media.Media;
|
||||
import javafx.scene.media.MediaException;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
import javafx.scene.media.MediaPlayer.Status;
|
||||
import static javafx.scene.media.MediaPlayer.Status.PAUSED;
|
||||
import static javafx.scene.media.MediaPlayer.Status.PLAYING;
|
||||
import static javafx.scene.media.MediaPlayer.Status.READY;
|
||||
import static javafx.scene.media.MediaPlayer.Status.STOPPED;
|
||||
import javafx.scene.media.MediaView;
|
||||
import javafx.util.Duration;
|
||||
import javax.swing.JPanel;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.openide.util.lookup.ServiceProviders;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.Installer;
|
||||
import org.sleuthkit.autopsy.corecomponents.FrameCapture;
|
||||
import org.sleuthkit.autopsy.corecomponents.VideoFrame;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.VideoUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Video viewer part of the Media View layered pane.
|
||||
*/
|
||||
@ServiceProviders(value = {
|
||||
@ServiceProvider(service = FrameCapture.class)
|
||||
})
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
public class FXVideoPanel extends MediaViewVideoPanel {
|
||||
|
||||
// Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html
|
||||
// for Javafx supported formats
|
||||
private static final String[] EXTENSIONS = new String[]{".m4v", ".fxm", ".flv", ".m3u8", ".mp4", ".aif", ".aiff", ".mp3", "m4a", ".wav"}; //NON-NLS
|
||||
private static final List<String> MIMETYPES = Arrays.asList("audio/x-aiff", "video/x-javafx", "video/x-flv", "application/vnd.apple.mpegurl", " audio/mpegurl", "audio/mpeg", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
|
||||
private static final Logger logger = Logger.getLogger(FXVideoPanel.class.getName());
|
||||
|
||||
private boolean fxInited = false;
|
||||
|
||||
private MediaPane mediaPane;
|
||||
|
||||
private AbstractFile currentFile;
|
||||
|
||||
public FXVideoPanel() {
|
||||
fxInited = Installer.isJavaFxInited();
|
||||
initComponents();
|
||||
if (fxInited) {
|
||||
Platform.runLater(() -> {
|
||||
|
||||
mediaPane = new MediaPane();
|
||||
Scene fxScene = new Scene(mediaPane);
|
||||
jFXPanel.setScene(fxScene);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public JPanel getVideoPanel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setupVideo(final AbstractFile file, final Dimension dims) {
|
||||
if (file.equals(currentFile)) {
|
||||
return;
|
||||
}
|
||||
if (!Case.isCaseOpen()) {
|
||||
//handle in-between condition when case is being closed
|
||||
//and an image was previously selected
|
||||
return;
|
||||
}
|
||||
reset();
|
||||
currentFile = file;
|
||||
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
|
||||
if (deleted) {
|
||||
mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.mediaPane.infoLabel"));
|
||||
removeAll();
|
||||
return;
|
||||
}
|
||||
mediaPane.setFit(dims);
|
||||
|
||||
String path = "";
|
||||
try {
|
||||
path = file.getUniquePath();
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Cannot get unique path of video file", ex); //NON-NLS
|
||||
}
|
||||
mediaPane.setInfoLabelText(path);
|
||||
mediaPane.setInfoLabelToolTipText(path);
|
||||
|
||||
final File tempFile;
|
||||
try {
|
||||
tempFile = VideoUtils.getVideoFileInTempDir(currentFile);
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
void reset() {
|
||||
Platform.runLater(() -> {
|
||||
if (mediaPane != null) {
|
||||
mediaPane.reset();
|
||||
}
|
||||
});
|
||||
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">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jFXPanel = new javafx.embed.swing.JFXPanel();
|
||||
|
||||
setBackground(new java.awt.Color(0, 0, 0));
|
||||
|
||||
javax.swing.GroupLayout jFXPanelLayout = new javax.swing.GroupLayout(jFXPanel);
|
||||
jFXPanel.setLayout(jFXPanelLayout);
|
||||
jFXPanelLayout.setHorizontalGroup(
|
||||
jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 400, Short.MAX_VALUE)
|
||||
);
|
||||
jFXPanelLayout.setVerticalGroup(
|
||||
jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 300, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javafx.embed.swing.JFXPanel jFXPanel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
@Override
|
||||
public boolean isInited() {
|
||||
return fxInited;
|
||||
}
|
||||
|
||||
private class MediaPane extends BorderPane {
|
||||
|
||||
private MediaPlayer mediaPlayer;
|
||||
|
||||
private final MediaView mediaView;
|
||||
|
||||
/**
|
||||
* The Duration of the media. *
|
||||
*/
|
||||
private Duration duration;
|
||||
|
||||
/**
|
||||
* The container for the media controls. *
|
||||
*/
|
||||
private final HBox mediaTools;
|
||||
|
||||
/**
|
||||
* The container for the media video output. *
|
||||
*/
|
||||
private final HBox mediaViewPane;
|
||||
|
||||
private final VBox controlPanel;
|
||||
|
||||
private final Slider progressSlider;
|
||||
|
||||
private final Button pauseButton;
|
||||
|
||||
private final Button stopButton;
|
||||
|
||||
private final Label progressLabel;
|
||||
|
||||
private final Label infoLabel;
|
||||
|
||||
private int totalHours;
|
||||
|
||||
private int totalMinutes;
|
||||
|
||||
private int totalSeconds;
|
||||
|
||||
private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
|
||||
|
||||
private static final String PLAY_TEXT = "►";
|
||||
|
||||
private static final String PAUSE_TEXT = "||";
|
||||
|
||||
private static final String STOP_TEXT = "X"; //NON-NLS
|
||||
|
||||
public MediaPane() {
|
||||
// Video Display
|
||||
mediaViewPane = new HBox();
|
||||
mediaViewPane.setStyle("-fx-background-color: black"); //NON-NLS
|
||||
mediaViewPane.setAlignment(Pos.CENTER);
|
||||
mediaView = new MediaView();
|
||||
mediaViewPane.getChildren().add(mediaView);
|
||||
setCenter(mediaViewPane);
|
||||
|
||||
// Media Controls
|
||||
controlPanel = new VBox();
|
||||
mediaTools = new HBox();
|
||||
mediaTools.setAlignment(Pos.CENTER);
|
||||
mediaTools.setPadding(new Insets(5, 10, 5, 10));
|
||||
|
||||
pauseButton = new Button(PLAY_TEXT);
|
||||
stopButton = new Button(STOP_TEXT);
|
||||
mediaTools.getChildren().add(pauseButton);
|
||||
mediaTools.getChildren().add(new Label(" "));
|
||||
mediaTools.getChildren().add(stopButton);
|
||||
mediaTools.getChildren().add(new Label(" "));
|
||||
progressSlider = new Slider();
|
||||
HBox.setHgrow(progressSlider, Priority.ALWAYS);
|
||||
progressSlider.setMinWidth(50);
|
||||
progressSlider.setMaxWidth(Double.MAX_VALUE);
|
||||
mediaTools.getChildren().add(progressSlider);
|
||||
progressLabel = new Label();
|
||||
progressLabel.setPrefWidth(135);
|
||||
progressLabel.setMinWidth(135);
|
||||
mediaTools.getChildren().add(progressLabel);
|
||||
|
||||
controlPanel.getChildren().add(mediaTools);
|
||||
controlPanel.setStyle("-fx-background-color: white"); //NON-NLS
|
||||
infoLabel = new Label("");
|
||||
controlPanel.getChildren().add(infoLabel);
|
||||
setBottom(controlPanel);
|
||||
setProgressActionListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this MediaPane.
|
||||
*
|
||||
*/
|
||||
public void reset() {
|
||||
if (mediaPlayer != null) {
|
||||
setInfoLabelText("");
|
||||
if (mediaPlayer.getStatus() == Status.PLAYING) {
|
||||
mediaPlayer.stop();
|
||||
}
|
||||
mediaPlayer = null;
|
||||
mediaView.setMediaPlayer(null);
|
||||
}
|
||||
resetProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Information Label of this MediaPane.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
public void setInfoLabelText(final String text) {
|
||||
logger.log(Level.INFO, "Setting Info Label Text: {0}", text); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
infoLabel.setText(text);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the MediaPane and it's components.
|
||||
*
|
||||
* @param dims the current dimensions of the DataContentViewer
|
||||
*/
|
||||
public void setFit(final Dimension dims) {
|
||||
Platform.runLater(() -> {
|
||||
setPrefSize(dims.getWidth(), dims.getHeight());
|
||||
// Set the Video output to fit the size allocated for it. give an
|
||||
// extra few px to ensure the info label will be shown
|
||||
mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the action listeners for the pause button and progress slider.
|
||||
*/
|
||||
private void setProgressActionListeners() {
|
||||
pauseButton.setOnAction(new EventHandler<ActionEvent>() {
|
||||
@Override
|
||||
public void handle(ActionEvent e) {
|
||||
if (mediaPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Status status = mediaPlayer.getStatus();
|
||||
|
||||
switch (status) {
|
||||
// If playing, pause
|
||||
case PLAYING:
|
||||
mediaPlayer.pause();
|
||||
break;
|
||||
// If ready, paused or stopped, continue playing
|
||||
case READY:
|
||||
case PAUSED:
|
||||
case STOPPED:
|
||||
mediaPlayer.play();
|
||||
break;
|
||||
default:
|
||||
logger.log(Level.INFO, "MediaPlayer in unexpected state: {0}", status.toString()); //NON-NLS
|
||||
// If the MediaPlayer is in an unexpected state, stop playback.
|
||||
mediaPlayer.stop();
|
||||
setInfoLabelText(NbBundle.getMessage(this.getClass(),
|
||||
"FXVideoPanel.pauseButton.infoLabel.playbackErr"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
stopButton.setOnAction((ActionEvent e) -> {
|
||||
if (mediaPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mediaPlayer.stop();
|
||||
});
|
||||
|
||||
progressSlider.valueProperty().addListener((Observable o) -> {
|
||||
if (mediaPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (progressSlider.isValueChanging()) {
|
||||
mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the progress label and slider to zero.
|
||||
*/
|
||||
private void resetProgress() {
|
||||
totalHours = 0;
|
||||
totalMinutes = 0;
|
||||
totalSeconds = 0;
|
||||
progressSlider.setValue(0.0);
|
||||
updateTime(Duration.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a MediaPlayer from the given Media URI.
|
||||
*
|
||||
* Also adds the necessary listeners to MediaPlayer events.
|
||||
*
|
||||
* @param mediaUri the location of the media.
|
||||
*
|
||||
* @return a MediaPlayer
|
||||
*/
|
||||
private MediaPlayer createMediaPlayer(String mediaUri) {
|
||||
Media media = new Media(mediaUri);
|
||||
|
||||
MediaPlayer player = new MediaPlayer(media);
|
||||
player.setOnReady(new ReadyListener());
|
||||
final Runnable pauseListener = () -> {
|
||||
pauseButton.setText(PLAY_TEXT);
|
||||
};
|
||||
player.setOnPaused(pauseListener);
|
||||
player.setOnStopped(pauseListener);
|
||||
player.setOnPlaying(() -> {
|
||||
pauseButton.setText(PAUSE_TEXT);
|
||||
});
|
||||
player.setOnEndOfMedia(new EndOfMediaListener());
|
||||
|
||||
player.currentTimeProperty().addListener((observable, oldTime, newTime) -> {
|
||||
updateSlider(newTime);
|
||||
updateTime(newTime);
|
||||
});
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress slider and label with the current time of the
|
||||
* media.
|
||||
*/
|
||||
private void updateProgress() {
|
||||
if (mediaPlayer == null) {
|
||||
return;
|
||||
}
|
||||
Duration currentTime = mediaPlayer.getCurrentTime();
|
||||
updateSlider(currentTime);
|
||||
updateTime(currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider with the current time.
|
||||
*
|
||||
* @param currentTime
|
||||
*/
|
||||
private void updateSlider(Duration currentTime) {
|
||||
if (progressSlider != null) {
|
||||
progressSlider.setDisable(currentTime.isUnknown());
|
||||
if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO)
|
||||
&& !progressSlider.isValueChanging()) {
|
||||
progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress label with the current time.
|
||||
*
|
||||
* @param currentTime
|
||||
*/
|
||||
private void updateTime(Duration currentTime) {
|
||||
long millisElapsed = (long) currentTime.toMillis();
|
||||
|
||||
long elapsedHours, elapsedMinutes, elapsedSeconds;
|
||||
// pick out the elapsed hours, minutes, seconds
|
||||
long secondsElapsed = millisElapsed / 1000;
|
||||
elapsedHours = (int) secondsElapsed / 3600;
|
||||
secondsElapsed -= elapsedHours * 3600;
|
||||
elapsedMinutes = (int) secondsElapsed / 60;
|
||||
secondsElapsed -= elapsedMinutes * 60;
|
||||
elapsedSeconds = (int) secondsElapsed;
|
||||
|
||||
String durationStr = String.format(durationFormat,
|
||||
elapsedHours, elapsedMinutes, elapsedSeconds,
|
||||
totalHours, totalMinutes, totalSeconds);
|
||||
Platform.runLater(() -> {
|
||||
progressLabel.setText(durationStr);
|
||||
});
|
||||
}
|
||||
|
||||
private void setInfoLabelToolTipText(final String text) {
|
||||
Platform.runLater(() -> {
|
||||
infoLabel.setTooltip(new Tooltip(text));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to MediaPlayer onReady events.
|
||||
*
|
||||
* Updates the progress label with the duration of the media.
|
||||
*/
|
||||
private class ReadyListener implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mediaPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
duration = mediaPlayer.getMedia().getDuration();
|
||||
long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis();
|
||||
|
||||
// pick out the total hours, minutes, seconds
|
||||
long durationSeconds = (int) durationInMillis / 1000;
|
||||
totalHours = (int) durationSeconds / 3600;
|
||||
durationSeconds -= totalHours * 3600;
|
||||
totalMinutes = (int) durationSeconds / 60;
|
||||
durationSeconds -= totalMinutes * 60;
|
||||
totalSeconds = (int) durationSeconds;
|
||||
updateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to MediaPlayer onEndOfMediaEvents.
|
||||
*
|
||||
* Prepares the media to be replayed.
|
||||
*/
|
||||
private class EndOfMediaListener implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mediaPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Duration beginning = mediaPlayer.getStartTime();
|
||||
mediaPlayer.stop();
|
||||
mediaPlayer.pause();
|
||||
pauseButton.setText(PLAY_TEXT);
|
||||
updateSlider(beginning);
|
||||
updateTime(beginning);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread that extracts Media from a Sleuthkit file representation to a
|
||||
* Java file representation that the Media Player can take as input.
|
||||
*/
|
||||
private class ExtractMedia extends Task<Long> {
|
||||
|
||||
private ProgressHandle progress;
|
||||
|
||||
private final AbstractFile sourceFile;
|
||||
|
||||
private final java.io.File tempFile;
|
||||
|
||||
ExtractMedia(AbstractFile sFile, java.io.File jFile) {
|
||||
this.sourceFile = sFile;
|
||||
this.tempFile = jFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URI of the media file.
|
||||
*
|
||||
* @return the URI of the media file.
|
||||
*/
|
||||
public String getMediaUri() {
|
||||
return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long call() throws Exception {
|
||||
if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
|
||||
progress = ProgressHandle.createHandle(
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"FXVideoPanel.progress.bufferingFile",
|
||||
sourceFile.getName()
|
||||
),
|
||||
() -> ExtractMedia.this.cancel(true));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.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;
|
||||
} finally {
|
||||
logger.log(Level.INFO, "Done buffering: {0}", tempFile.getName()); //NON-NLS
|
||||
}
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
super.failed();
|
||||
onDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
onDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
super.cancelled();
|
||||
onDone();
|
||||
}
|
||||
|
||||
private void onDone() {
|
||||
progressLabel.setText("");
|
||||
try {
|
||||
super.get(); //block and get all exceptions thrown while doInBackground()
|
||||
} catch (CancellationException ex) {
|
||||
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingCancelled"));
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingInterrupted"));
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.errorWritingVideoToDisk"));
|
||||
} finally {
|
||||
if (null != progress) {
|
||||
progress.finish();
|
||||
}
|
||||
if (!this.isCancelled()) {
|
||||
logger.log(Level.INFO, "ExtractMedia is done: {0}", tempFile.getName()); //NON-NLS
|
||||
try {
|
||||
mediaPane.mediaPlayer = mediaPane.createMediaPlayer(getMediaUri());
|
||||
mediaView.setMediaPlayer(mediaPane.mediaPlayer);
|
||||
} catch (MediaException ex) {
|
||||
progressLabel.setText("");
|
||||
mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file a video file from which to capture frames
|
||||
* @param numFrames the number of frames to capture. These frames will be
|
||||
* captured at successive intervals given by
|
||||
* durationOfVideo/numFrames. If this frame interval is
|
||||
* less than MIN_FRAME_INTERVAL_MILLIS, then only one frame
|
||||
* will be captured and returned.
|
||||
*
|
||||
* @return a List of VideoFrames representing the captured frames.
|
||||
*/
|
||||
@Override
|
||||
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
|
||||
//What is/was the point of this method /interface.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getExtensions() {
|
||||
return EXTENSIONS.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMimeTypes() {
|
||||
return MIMETYPES;
|
||||
}
|
||||
}
|
797
Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java
Normal file
797
Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java
Normal file
@ -0,0 +1,797 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 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 com.google.common.io.Files;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import org.gstreamer.ClockTime;
|
||||
import org.gstreamer.Gst;
|
||||
import org.gstreamer.GstException;
|
||||
import org.gstreamer.State;
|
||||
import org.gstreamer.StateChangeReturn;
|
||||
import org.gstreamer.elements.PlayBin2;
|
||||
import org.gstreamer.elements.RGBDataSink;
|
||||
import org.gstreamer.swing.VideoComponent;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.openide.util.lookup.ServiceProviders;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.corecomponents.FrameCapture;
|
||||
import org.sleuthkit.autopsy.corecomponents.VideoFrame;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.VideoUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
@ServiceProviders(value = {
|
||||
@ServiceProvider(service = FrameCapture.class)
|
||||
})
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
public class GstVideoPanel extends MediaViewVideoPanel {
|
||||
|
||||
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
|
||||
private static final List<String> MIMETYPES = Arrays.asList("video/quicktime", "audio/mpeg", "audio/x-mpeg", "video/mpeg", "video/x-mpeg", "audio/mpeg3", "audio/x-mpeg-3", "video/x-flv", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GstVideoPanel.class.getName());
|
||||
private boolean gstInited;
|
||||
private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
|
||||
private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000;
|
||||
private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.cannotProcFile.err");
|
||||
//playback
|
||||
private long durationMillis = 0;
|
||||
private VideoProgressWorker videoProgressWorker;
|
||||
private int totalHours, totalMinutes, totalSeconds;
|
||||
private volatile PlayBin2 gstPlaybin2;
|
||||
private VideoComponent gstVideoComponent;
|
||||
private boolean autoTracking = false; // true if the slider is moving automatically
|
||||
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
|
||||
private AbstractFile currentFile;
|
||||
private final Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
/**
|
||||
* Creates new form MediaViewVideoPanel
|
||||
*/
|
||||
public GstVideoPanel() {
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
public JButton getPauseButton() {
|
||||
return pauseButton;
|
||||
}
|
||||
|
||||
public JLabel getProgressLabel() {
|
||||
return progressLabel;
|
||||
}
|
||||
|
||||
public JSlider getProgressSlider() {
|
||||
return progressSlider;
|
||||
}
|
||||
|
||||
public JPanel getVideoPanel() {
|
||||
return videoPanel;
|
||||
}
|
||||
|
||||
public VideoComponent getVideoComponent() {
|
||||
return gstVideoComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInited() {
|
||||
return gstInited;
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
if (!initGst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
|
||||
progressSlider.setValue(0);
|
||||
|
||||
progressSlider.addChangeListener((ChangeEvent e) -> {
|
||||
/**
|
||||
* Should always try to synchronize any call to
|
||||
* progressSlider.setValue() to avoid a different thread changing
|
||||
* playbin while stateChanged() is processing
|
||||
*/
|
||||
int time = progressSlider.getValue();
|
||||
synchronized (playbinLock) {
|
||||
if (gstPlaybin2 != null && !autoTracking) {
|
||||
State orig = gstPlaybin2.getState();
|
||||
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
gstPlaybin2.setState(orig);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean initGst() {
|
||||
try {
|
||||
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); //NON-NLS
|
||||
Gst.init();
|
||||
gstInited = true;
|
||||
} catch (GstException e) {
|
||||
gstInited = false;
|
||||
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(this.getClass(), "GstVideoPanel.initGst.gstException.msg"),
|
||||
e.getMessage());
|
||||
return false;
|
||||
} catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
|
||||
gstInited = false;
|
||||
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(this.getClass(), "GstVideoPanel.initGst.otherException.msg"),
|
||||
e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages ({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
|
||||
void setupVideo(final AbstractFile file, final Dimension dims) {
|
||||
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"); //NON-NLS
|
||||
}
|
||||
infoLabel.setText(path);
|
||||
infoLabel.setToolTipText(path);
|
||||
pauseButton.setEnabled(true);
|
||||
progressSlider.setEnabled(true);
|
||||
|
||||
|
||||
gstVideoComponent = new VideoComponent();
|
||||
synchronized (playbinLock) {
|
||||
if (gstPlaybin2 != null) {
|
||||
gstPlaybin2.dispose();
|
||||
}
|
||||
gstPlaybin2 = new PlayBin2("VideoPlayer"); //NON-NLS
|
||||
gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
|
||||
|
||||
videoPanel.removeAll();
|
||||
|
||||
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
|
||||
videoPanel.add(gstVideoComponent);
|
||||
|
||||
videoPanel.setVisible(true);
|
||||
|
||||
gstPlaybin2.setInputFile(ioFile);
|
||||
|
||||
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
void reset() {
|
||||
|
||||
// reset the progress label text on the event dispatch thread
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressLabel.setText("");
|
||||
});
|
||||
|
||||
if (!isInited()) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (playbinLock) {
|
||||
if (gstPlaybin2 != null) {
|
||||
if (gstPlaybin2.isPlaying()) {
|
||||
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
if (gstPlaybin2.getState().equals(State.NULL)) {
|
||||
gstPlaybin2.dispose();
|
||||
}
|
||||
gstPlaybin2 = null;
|
||||
}
|
||||
gstVideoComponent = null;
|
||||
}
|
||||
|
||||
// get rid of any existing videoProgressWorker thread
|
||||
if (videoProgressWorker != null) {
|
||||
videoProgressWorker.cancel(true);
|
||||
videoProgressWorker = null;
|
||||
}
|
||||
|
||||
currentFile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file a video file from which to capture frames
|
||||
* @param numFrames the number of frames to capture. These frames will be
|
||||
* captured at successive intervals given by
|
||||
* durationOfVideo/numFrames. If this frame interval is
|
||||
* less than MIN_FRAME_INTERVAL_MILLIS, then only one frame
|
||||
* will be captured and returned.
|
||||
*
|
||||
* @return a List of VideoFrames representing the captured frames.
|
||||
*/
|
||||
@Override
|
||||
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
|
||||
|
||||
List<VideoFrame> frames = new ArrayList<>();
|
||||
|
||||
Object lock = new Object();
|
||||
FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock);
|
||||
|
||||
if (!isInited()) {
|
||||
return frames;
|
||||
}
|
||||
|
||||
// throw exception if this file is known to be problematic
|
||||
if (badVideoFiles.contains(file.getName())) {
|
||||
throw new Exception(
|
||||
NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemFile.msg", file.getName()));
|
||||
}
|
||||
|
||||
// set up a PlayBin2 object
|
||||
RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener); //NON-NLS
|
||||
PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); //NON-NLS
|
||||
playbin.setInputFile(file);
|
||||
playbin.setVideoSink(videoSink);
|
||||
|
||||
// this is necessary to get a valid duration value
|
||||
StateChangeReturn ret = playbin.play();
|
||||
if (ret == StateChangeReturn.FAILURE) {
|
||||
// add this file to the set of known bad ones
|
||||
badVideoFiles.add(file.getName());
|
||||
throw new Exception(NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPlay.msg"));
|
||||
}
|
||||
ret = playbin.pause();
|
||||
if (ret == StateChangeReturn.FAILURE) {
|
||||
// add this file to the set of known bad ones
|
||||
badVideoFiles.add(file.getName());
|
||||
throw new Exception(NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPause.msg"));
|
||||
}
|
||||
playbin.getState();
|
||||
|
||||
// get the duration of the video
|
||||
TimeUnit unit = TimeUnit.MILLISECONDS;
|
||||
long myDurationMillis = playbin.queryDuration(unit);
|
||||
if (myDurationMillis <= 0) {
|
||||
return frames;
|
||||
}
|
||||
|
||||
// calculate the number of frames to capture
|
||||
int numFramesToGet = numFrames;
|
||||
long frameInterval = myDurationMillis / numFrames;
|
||||
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
|
||||
numFramesToGet = 1;
|
||||
}
|
||||
|
||||
// for each timeStamp, grap a frame
|
||||
for (int i = 0; i < numFramesToGet; ++i) {
|
||||
long timeStamp = i * frameInterval;
|
||||
|
||||
ret = playbin.pause();
|
||||
if (ret == StateChangeReturn.FAILURE) {
|
||||
// add this file to the set of known bad ones
|
||||
badVideoFiles.add(file.getName());
|
||||
throw new Exception(
|
||||
NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPauseCaptFrame.msg"));
|
||||
}
|
||||
playbin.getState();
|
||||
|
||||
if (!playbin.seek(timeStamp, unit)) {
|
||||
logger.log(Level.INFO, "There was a problem seeking to {0} {1}", new Object[]{timeStamp, unit.name().toLowerCase()}); //NON-NLS
|
||||
}
|
||||
|
||||
ret = playbin.play();
|
||||
if (ret == StateChangeReturn.FAILURE) {
|
||||
// add this file to the set of known bad ones
|
||||
badVideoFiles.add(file.getName());
|
||||
throw new Exception(
|
||||
NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPlayCaptFrame.msg"));
|
||||
}
|
||||
|
||||
// wait for FrameCaptureRGBListener to finish
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e); //NON-NLS
|
||||
}
|
||||
}
|
||||
Image image = rgbListener.getImage();
|
||||
|
||||
ret = playbin.stop();
|
||||
if (ret == StateChangeReturn.FAILURE) {
|
||||
// add this file to the set of known bad ones
|
||||
badVideoFiles.add(file.getName());
|
||||
throw new Exception(
|
||||
NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemStopCaptFrame.msg"));
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file {0}", file.getName()); //NON-NLS
|
||||
badVideoFiles.add(file.getName());
|
||||
break;
|
||||
}
|
||||
|
||||
frames.add(new VideoFrame(image, timeStamp));
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
private class FrameCaptureRGBListener implements RGBDataSink.Listener {
|
||||
|
||||
public FrameCaptureRGBListener(Object waiter) {
|
||||
this.waiter = waiter;
|
||||
}
|
||||
|
||||
private BufferedImage bi;
|
||||
private final Object waiter;
|
||||
|
||||
@Override
|
||||
public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
|
||||
synchronized (waiter) {
|
||||
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
|
||||
waiter.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
synchronized (waiter) {
|
||||
Image image = bi;
|
||||
bi = null;
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(GstVideoPanel.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(GstVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(GstVideoPanel.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) {
|
||||
State state = gstPlaybin2.getState();
|
||||
if (state.equals(State.PLAYING)) {
|
||||
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
pauseButton.setText("►");
|
||||
// Is this call necessary considering we just called gstPlaybin2.pause()?
|
||||
if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
} else if (state.equals(State.PAUSED)) {
|
||||
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
pauseButton.setText("||");
|
||||
// Is this call necessary considering we just called gstPlaybin2.play()?
|
||||
if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
} else if (state.equals(State.READY)) {
|
||||
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;
|
||||
}
|
||||
|
||||
new ExtractMedia(currentFile, tempVideoFile).execute();
|
||||
|
||||
}
|
||||
}
|
||||
}//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
|
||||
|
||||
private class VideoProgressWorker extends SwingWorker<Object, Object> {
|
||||
|
||||
private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
|
||||
private long millisElapsed = 0;
|
||||
private final long INTER_FRAME_PERIOD_MS = 20;
|
||||
private final long END_TIME_MARGIN_MS = 50;
|
||||
|
||||
private boolean isPlayBinReady() {
|
||||
synchronized (playbinLock) {
|
||||
return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetVideo() throws Exception {
|
||||
synchronized (playbinLock) {
|
||||
if (gstPlaybin2 != null) {
|
||||
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
}
|
||||
// ready to be played again
|
||||
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
}
|
||||
gstPlaybin2.getState(); //NEW
|
||||
}
|
||||
}
|
||||
pauseButton.setText("►");
|
||||
progressSlider.setValue(0);
|
||||
|
||||
String durationStr = String.format(durationFormat, 0, 0, 0,
|
||||
totalHours, totalMinutes, totalSeconds);
|
||||
progressLabel.setText(durationStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true while millisElapsed is greater than END_TIME_MARGIN_MS
|
||||
* from durationMillis. This is used to indicate when the video
|
||||
* has ended because for some videos the time elapsed never
|
||||
* becomes equal to the reported duration of the video.
|
||||
*/
|
||||
private boolean hasNotEnded() {
|
||||
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
|
||||
// enable the slider
|
||||
progressSlider.setEnabled(true);
|
||||
|
||||
ClockTime pos;
|
||||
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
|
||||
|
||||
synchronized (playbinLock) {
|
||||
pos = gstPlaybin2.queryPosition();
|
||||
}
|
||||
millisElapsed = pos.toMillis();
|
||||
|
||||
// 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 durationStr = String.format(durationFormat,
|
||||
elapsedHours, elapsedMinutes, elapsedSeconds,
|
||||
totalHours, totalMinutes, totalSeconds);
|
||||
|
||||
progressLabel.setText(durationStr);
|
||||
autoTracking = true;
|
||||
progressSlider.setValue((int) millisElapsed);
|
||||
autoTracking = false;
|
||||
|
||||
try {
|
||||
Thread.sleep(INTER_FRAME_PERIOD_MS);
|
||||
} catch (InterruptedException ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// disable the slider
|
||||
progressSlider.setEnabled(false);
|
||||
|
||||
resetVideo();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
// see if any exceptions were thrown
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.WARNING, "Error updating video progress: {0}", ex.getMessage()); //NON-NLS
|
||||
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.infoLabel.updateErr",
|
||||
ex.getMessage()));
|
||||
} // catch and ignore if we were cancelled
|
||||
catch (java.util.concurrent.CancellationException ex) {
|
||||
}
|
||||
}
|
||||
} //end class progress worker
|
||||
|
||||
/*
|
||||
* Thread that extracts and plays a file
|
||||
*/
|
||||
private class ExtractMedia extends SwingWorker<Long, Void> {
|
||||
|
||||
private ProgressHandle progress;
|
||||
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(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> ExtractMedia.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 (Exception 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() {
|
||||
if (tempFile == null || !tempFile.exists()) {
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
|
||||
return;
|
||||
}
|
||||
ClockTime dur;
|
||||
synchronized (playbinLock) {
|
||||
// must play, then pause and get state to get duration.
|
||||
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
return;
|
||||
}
|
||||
gstPlaybin2.getState();
|
||||
dur = gstPlaybin2.queryDuration();
|
||||
}
|
||||
durationMillis = dur.toMillis();
|
||||
|
||||
// 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;
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressSlider.setMaximum((int) durationMillis);
|
||||
progressSlider.setMinimum(0);
|
||||
|
||||
synchronized (playbinLock) {
|
||||
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
|
||||
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
|
||||
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
|
||||
}
|
||||
}
|
||||
pauseButton.setText("||");
|
||||
videoProgressWorker = new VideoProgressWorker();
|
||||
videoProgressWorker.execute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getExtensions() {
|
||||
return EXTENSIONS.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMimeTypes() {
|
||||
return MIMETYPES;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -36,24 +36,24 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName());
|
||||
private AbstractFile lastFile;
|
||||
//UI
|
||||
private final MediaPlayerPanel mediaPlayerPanel;
|
||||
private final boolean mediaPlayerPanelInited;
|
||||
private final MediaViewVideoPanel videoPanel;
|
||||
private final boolean videoPanelInited;
|
||||
private final MediaViewImagePanel imagePanel;
|
||||
private final boolean imagePanelInited;
|
||||
|
||||
private static final String IMAGE_VIEWER_LAYER = "IMAGE"; //NON-NLS
|
||||
private static final String MEDIA_PLAYER_LAYER = "AUDIO_VIDEO"; //NON-NLS
|
||||
private static final String VIDEO_VIEWER_LAYER = "VIDEO"; //NON-NLS
|
||||
|
||||
/**
|
||||
* Creates a new MediaFileViewer.
|
||||
* Creates new form DataContentViewerVideo
|
||||
*/
|
||||
public MediaFileViewer() {
|
||||
|
||||
initComponents();
|
||||
|
||||
// get the right panel for our platform
|
||||
mediaPlayerPanel = new MediaPlayerPanel();
|
||||
mediaPlayerPanelInited = mediaPlayerPanel.isInited();
|
||||
videoPanel = MediaViewVideoPanel.createVideoPanel();
|
||||
videoPanelInited = videoPanel.isInited();
|
||||
|
||||
imagePanel = new MediaViewImagePanel();
|
||||
imagePanelInited = imagePanel.isInited();
|
||||
@ -64,9 +64,9 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
private void customizeComponents() {
|
||||
add(imagePanel, IMAGE_VIEWER_LAYER);
|
||||
add(mediaPlayerPanel, MEDIA_PLAYER_LAYER);
|
||||
add(videoPanel, VIDEO_VIEWER_LAYER);
|
||||
|
||||
showImagePanel();
|
||||
showVideoPanel(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,8 +94,8 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
List<String> mimeTypes = new ArrayList<>();
|
||||
|
||||
mimeTypes.addAll(this.imagePanel.getSupportedMimeTypes());
|
||||
mimeTypes.addAll(this.mediaPlayerPanel.getSupportedMimeTypes());
|
||||
mimeTypes.addAll(this.imagePanel.getMimeTypes());
|
||||
mimeTypes.addAll(this.videoPanel.getMimeTypes());
|
||||
|
||||
return mimeTypes;
|
||||
}
|
||||
@ -123,12 +123,12 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
final Dimension dims = MediaFileViewer.this.getSize();
|
||||
//logger.info("setting node on media viewer"); //NON-NLS
|
||||
if (mediaPlayerPanelInited && mediaPlayerPanel.isSupported(file)) {
|
||||
mediaPlayerPanel.loadFile(file, dims);
|
||||
this.showVideoPanel();
|
||||
if (videoPanelInited && videoPanel.isSupported(file)) {
|
||||
videoPanel.setupVideo(file, dims);
|
||||
this.showVideoPanel(true);
|
||||
} else if (imagePanelInited && imagePanel.isSupported(file)) {
|
||||
imagePanel.showImageFx(file, dims);
|
||||
this.showImagePanel();
|
||||
this.showVideoPanel(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while setting node", e); //NON-NLS
|
||||
@ -136,20 +136,18 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the media player panel.
|
||||
* switch to visible video or image panel
|
||||
*
|
||||
* @param showVideo true if video panel, false if image panel
|
||||
*/
|
||||
private void showVideoPanel() {
|
||||
CardLayout layout = (CardLayout) this.getLayout();
|
||||
layout.show(this, MEDIA_PLAYER_LAYER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the image panel.
|
||||
*/
|
||||
private void showImagePanel() {
|
||||
private void showVideoPanel(boolean showVideo) {
|
||||
CardLayout layout = (CardLayout) this.getLayout();
|
||||
if (showVideo) {
|
||||
layout.show(this, VIDEO_VIEWER_LAYER);
|
||||
} else {
|
||||
layout.show(this, IMAGE_VIEWER_LAYER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getComponent() {
|
||||
@ -158,27 +156,24 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
@Override
|
||||
public void resetComponent() {
|
||||
mediaPlayerPanel.reset();
|
||||
videoPanel.reset();
|
||||
imagePanel.reset();
|
||||
lastFile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel used to display media content.
|
||||
*/
|
||||
protected interface MediaViewPanel {
|
||||
interface MediaViewPanel {
|
||||
|
||||
/**
|
||||
* @return supported mime types
|
||||
*/
|
||||
List<String> getSupportedMimeTypes();
|
||||
List<String> getMimeTypes();
|
||||
|
||||
/**
|
||||
* returns supported extensions (each starting with .)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<String> getSupportedExtensions();
|
||||
List<String> getExtensionsList();
|
||||
|
||||
boolean isSupported(AbstractFile file);
|
||||
}
|
||||
|
@ -1,677 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-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 com.google.common.io.Files;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import org.freedesktop.gstreamer.ClockTime;
|
||||
import org.freedesktop.gstreamer.Gst;
|
||||
import org.freedesktop.gstreamer.GstException;
|
||||
import org.freedesktop.gstreamer.State;
|
||||
import org.freedesktop.gstreamer.StateChangeReturn;
|
||||
import org.freedesktop.gstreamer.elements.PlayBin;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.VideoUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* This is a video player that is part of the Media View layered pane. It uses
|
||||
* GStreamer to process the video and JavaFX to display it.
|
||||
*/
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel {
|
||||
|
||||
private static final String[] FILE_EXTENSIONS = new String[] {
|
||||
".3g2",
|
||||
".3gp",
|
||||
".3gpp",
|
||||
".aac",
|
||||
".aif",
|
||||
".aiff",
|
||||
".amr",
|
||||
".asf",
|
||||
".au",
|
||||
".avi",
|
||||
".flac",
|
||||
".flv",
|
||||
".m4a",
|
||||
".m4v",
|
||||
".mka",
|
||||
".mkv",
|
||||
".mov",
|
||||
".mp2",
|
||||
".mp3",
|
||||
".mp4",
|
||||
".mpeg",
|
||||
".mpg",
|
||||
".mxf",
|
||||
".ogg",
|
||||
".wav",
|
||||
".webm",
|
||||
".wma",
|
||||
".wmv",
|
||||
}; //NON-NLS
|
||||
private static final List<String> MIME_TYPES = Arrays.asList(
|
||||
"video/3gpp",
|
||||
"video/3gpp2",
|
||||
"audio/aiff",
|
||||
"audio/amr-wb",
|
||||
"audio/basic",
|
||||
"audio/mp4",
|
||||
"video/mp4",
|
||||
"audio/mpeg",
|
||||
"video/mpeg",
|
||||
"audio/mpeg3",
|
||||
"application/mxf",
|
||||
"application/ogg",
|
||||
"video/quicktime",
|
||||
"audio/vorbis",
|
||||
"audio/vnd.wave",
|
||||
"video/webm",
|
||||
"video/x-3ivx",
|
||||
"audio/x-aac",
|
||||
"audio/x-adpcm",
|
||||
"audio/x-alaw",
|
||||
"audio/x-cinepak",
|
||||
"video/x-divx",
|
||||
"audio/x-dv",
|
||||
"video/x-dv",
|
||||
"video/x-ffv",
|
||||
"audio/x-flac",
|
||||
"video/x-flv",
|
||||
"audio/x-gsm",
|
||||
"video/x-h263",
|
||||
"video/x-h264",
|
||||
"video/x-huffyuv",
|
||||
"video/x-indeo",
|
||||
"video/x-intel-h263",
|
||||
"audio/x-ircam",
|
||||
"video/x-jpeg",
|
||||
"audio/x-m4a",
|
||||
"video/x-m4v",
|
||||
"audio/x-mace",
|
||||
"audio/x-matroska",
|
||||
"video/x-matroska",
|
||||
"audio/x-mpeg",
|
||||
"video/x-mpeg",
|
||||
"audio/x-mpeg-3",
|
||||
"video/x-ms-asf",
|
||||
"audio/x-ms-wma",
|
||||
"video/x-ms-wmv",
|
||||
"video/x-msmpeg",
|
||||
"video/x-msvideo",
|
||||
"video/x-msvideocodec",
|
||||
"audio/x-mulaw",
|
||||
"audio/x-nist",
|
||||
"audio/x-oggflac",
|
||||
"audio/x-paris",
|
||||
"audio/x-qdm2",
|
||||
"audio/x-raw",
|
||||
"video/x-raw",
|
||||
"video/x-rle",
|
||||
"audio/x-speex",
|
||||
"video/x-svq",
|
||||
"audio/x-svx",
|
||||
"video/x-tarkin",
|
||||
"video/x-theora",
|
||||
"audio/x-voc",
|
||||
"audio/x-vorbis",
|
||||
"video/x-vp3",
|
||||
"audio/x-w64",
|
||||
"audio/x-wav",
|
||||
"audio/x-wma",
|
||||
"video/x-wmv",
|
||||
"video/x-xvid"
|
||||
); //NON-NLS
|
||||
|
||||
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, "GstVideoPanel.cannotProcFile.err");
|
||||
//playback
|
||||
private long durationMillis = 0;
|
||||
private int totalHours, totalMinutes, totalSeconds;
|
||||
private volatile PlayBin gstPlayBin;
|
||||
private GstVideoRendererPanel gstVideoRenderer;
|
||||
private final Object playbinLock = new Object(); // lock for synchronization of gstPlayBin player
|
||||
private AbstractFile currentFile;
|
||||
|
||||
private Timer timer;
|
||||
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
|
||||
*/
|
||||
public MediaPlayerPanel() {
|
||||
initComponents();
|
||||
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() {
|
||||
if (!initGst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
|
||||
progressSlider.setMinimum(0);
|
||||
progressSlider.setMaximum(2000);
|
||||
progressSlider.setValue(0);
|
||||
progressSlider.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent event) {
|
||||
if (gstPlayBin == null) {
|
||||
return;
|
||||
}
|
||||
if (progressSlider.getValueIsAdjusting()) {
|
||||
synchronized (playbinLock) {
|
||||
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
|
||||
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
||||
if (duration > 0) {
|
||||
double relativePosition = progressSlider.getValue() / 2000.0;
|
||||
gstPlayBin.seek((long) (relativePosition * duration), TimeUnit.NANOSECONDS);
|
||||
} else if (position > 0 || progressSlider.getValue() > 0) {
|
||||
gstPlayBin.seek(ClockTime.ZERO);
|
||||
progressSlider.setValue(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean initGst() {
|
||||
try {
|
||||
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); //NON-NLS
|
||||
Gst.init();
|
||||
gstInited = true;
|
||||
} catch (GstException 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.add(gstVideoRenderer);//add jfx ui to JPanel
|
||||
|
||||
videoPanel.setVisible(true);
|
||||
|
||||
gstPlayBin.setInputFile(ioFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare this MediaViewVideoPanel to accept a different media file.
|
||||
*/
|
||||
void reset() {
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
// reset the progress label text on the event dispatch thread
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
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 = null;
|
||||
}
|
||||
gstVideoRenderer = null;
|
||||
}
|
||||
|
||||
progressSlider.setValue(0);
|
||||
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) {
|
||||
extractMediaWorker.cancel(true);
|
||||
extractMediaWorker = null;
|
||||
}
|
||||
extractMediaWorker = new ExtractMedia(currentFile, tempVideoFile);
|
||||
extractMediaWorker.execute();
|
||||
|
||||
}
|
||||
}
|
||||
}//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
|
||||
*/
|
||||
private class ExtractMedia extends SwingWorker<Long, Void> {
|
||||
|
||||
private ProgressHandle progress;
|
||||
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() {
|
||||
if (tempFile == null || !tempFile.exists()) {
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
|
||||
return;
|
||||
}
|
||||
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
|
||||
public List<String> getSupportedExtensions() {
|
||||
return Arrays.asList(FILE_EXTENSIONS.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSupportedMimeTypes() {
|
||||
return MIME_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(AbstractFile file) {
|
||||
String extension = file.getNameExtension();
|
||||
/**
|
||||
* Although it seems too restrictive, requiring both a supported
|
||||
* extension and a supported MIME type prevents two undesirable
|
||||
* behaviors:
|
||||
*
|
||||
* 1) Until AUT-1766 and AUT-1801 are fixed, we incorrectly identify all
|
||||
* iff files as audio/aiff. This means that if this panel went with the
|
||||
* looser 'mime type OR extension' criteria we use for images, then this
|
||||
* panel would attempt (and fail) to display all iff files, even non
|
||||
* audio ones.
|
||||
*
|
||||
* 2) The looser criteria means we are less confident about the files we
|
||||
* are potentialy sending to GStreamer on 32bit jvms. We are less
|
||||
* comfortable with the error handling for GStreamer, and don't want to
|
||||
* send it files which might cause it trouble.
|
||||
*/
|
||||
if (getSupportedExtensions().contains("." + extension)) {
|
||||
SortedSet<String> mimeTypes = new TreeSet<>(getSupportedMimeTypes());
|
||||
try {
|
||||
String mimeType = new FileTypeDetector().getMIMEType(file);
|
||||
return mimeTypes.contains(mimeType);
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
|
||||
if (!mimeTypes.isEmpty() && file.isMimeType(mimeTypes) == AbstractFile.MimeMatchEnum.TRUE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return getSupportedExtensions().contains("." + extension);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -245,7 +245,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
* @return supported mime types
|
||||
*/
|
||||
@Override
|
||||
public List<String> getSupportedMimeTypes() {
|
||||
public List<String> getMimeTypes() {
|
||||
return Collections.unmodifiableList(Lists.newArrayList(supportedMimes));
|
||||
}
|
||||
|
||||
@ -255,7 +255,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<String> getSupportedExtensions() {
|
||||
public List<String> getExtensionsList() {
|
||||
return getExtensions();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 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.awt.Dimension;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JPanel;
|
||||
import org.sleuthkit.autopsy.corecomponents.FrameCapture;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
/**
|
||||
* Video viewer part of the Media View layered pane. Uses different engines
|
||||
* depending on platform.
|
||||
*/
|
||||
abstract class MediaViewVideoPanel extends JPanel implements FrameCapture, MediaFileViewer.MediaViewPanel {
|
||||
|
||||
private static final Set<String> AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
|
||||
|
||||
// 64 bit architectures
|
||||
private static final String[] ARCH64 = new String[]{"amd64", "x86_64"}; //NON-NLS NON-NLS
|
||||
|
||||
// 32 bit architectures
|
||||
private static final String[] ARCH32 = new String[]{"x86"}; //NON-NLS
|
||||
|
||||
/**
|
||||
* Factory Method to create a MediaViewVideoPanel.
|
||||
*
|
||||
* Implementation is dependent on the architecture of the JVM.
|
||||
*
|
||||
* @return a MediaViewVideoPanel instance.
|
||||
*/
|
||||
public static MediaViewVideoPanel createVideoPanel() {
|
||||
if (is64BitJVM()) {
|
||||
logger.log(Level.INFO, "64 bit JVM detected. Creating JavaFX Video Player."); //NON-NLS
|
||||
return getFXImpl();
|
||||
} else {
|
||||
logger.log(Level.INFO, "32 bit JVM detected. Creating GStreamer Video Player."); //NON-NLS
|
||||
return getGstImpl();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the JVM architecture 64 bit?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static boolean is64BitJVM() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
return Arrays.asList(ARCH64).contains(arch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a GStreamer video player implementation.
|
||||
*
|
||||
* @return a GstVideoPanel
|
||||
*/
|
||||
private static MediaViewVideoPanel getGstImpl() {
|
||||
return new GstVideoPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JavaFX video player implementation.
|
||||
*
|
||||
* @return a FXVideoPanel
|
||||
*/
|
||||
private static MediaViewVideoPanel getFXImpl() {
|
||||
return new FXVideoPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Has this MediaViewVideoPanel been initialized correctly?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract boolean isInited();
|
||||
|
||||
/**
|
||||
* Prepare this MediaViewVideoPanel to accept a different media file.
|
||||
*/
|
||||
abstract void reset();
|
||||
|
||||
/**
|
||||
* Initialize all the necessary vars to play a video/audio file.
|
||||
*
|
||||
* @param file video file to play
|
||||
* @param dims dimension of the parent window
|
||||
*/
|
||||
abstract void setupVideo(final AbstractFile file, final Dimension dims);
|
||||
|
||||
/**
|
||||
* Return the extensions supported by this video panel.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
abstract public String[] getExtensions();
|
||||
|
||||
/**
|
||||
* Return the MimeTypes supported by this video panel.
|
||||
*/
|
||||
@Override
|
||||
abstract public List<String> getMimeTypes();
|
||||
|
||||
@Override
|
||||
public List<String> getExtensionsList() {
|
||||
return Arrays.asList(getExtensions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(AbstractFile file) {
|
||||
String extension = file.getNameExtension();
|
||||
/**
|
||||
* Although it seems too restrictive, requiring both a supported
|
||||
* extension and a supported MIME type prevents two undesirable
|
||||
* behaviors:
|
||||
*
|
||||
* 1) Until AUT-1766 and AUT-1801 are fixed, we incorrectly identify all
|
||||
* iff files as audio/aiff. This means that if this panel went with the
|
||||
* looser 'mime type OR extension' criteria we use for images, then this
|
||||
* panel would attempt (and fail) to display all iff files, even non
|
||||
* audio ones.
|
||||
*
|
||||
* 2) The looser criteria means we are less confident about the files we
|
||||
* are potentialy sending to GStreamer on 32bit jvms. We are less
|
||||
* comfortable with the error handling for GStreamer, and don't want to
|
||||
* send it files which might cause it trouble.
|
||||
*/
|
||||
if (AUDIO_EXTENSIONS.contains("." + extension) || getExtensionsList().contains("." + extension)) {
|
||||
SortedSet<String> mimeTypes = new TreeSet<>(getMimeTypes());
|
||||
try {
|
||||
String mimeType = new FileTypeDetector().getMIMEType(file);
|
||||
return mimeTypes.contains(mimeType);
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
|
||||
if (!mimeTypes.isEmpty() && file.isMimeType(mimeTypes) == AbstractFile.MimeMatchEnum.TRUE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return getExtensionsList().contains("." + extension);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 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.corecomponents;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface used to capture frames from a video file.
|
||||
*/
|
||||
public interface FrameCapture {
|
||||
|
||||
/**
|
||||
* @param file the video file to use
|
||||
* @param numFrames the number of frames to capture. Note that the actual
|
||||
* number of frames returned may be less than this number.
|
||||
* Specifically, this may happen if the video is very
|
||||
* short.
|
||||
*
|
||||
* @return a list of VideoFrames representing the captured frames
|
||||
*/
|
||||
List<VideoFrame> captureFrames(File file, int numFrames) throws Exception;
|
||||
|
||||
}
|
31
Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java
Executable file
31
Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java
Executable file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.corecomponents;
|
||||
|
||||
/**
|
||||
* This class exists to support backwards compatibility of an erroneous call to
|
||||
* Logger.getLogger(GSTVideoPanel.class.getName()) in OpenCVFrameCapture.java in
|
||||
* an older version of the Video Triage Net Beans Module (NBM). It should be
|
||||
* removed when we are ready to stop supporting older Video Triage NBMs. The
|
||||
* current Video Triage code has already been updated.
|
||||
*/
|
||||
@Deprecated
|
||||
public class GSTVideoPanel {
|
||||
|
||||
}
|
@ -12,9 +12,7 @@
|
||||
<dependency org="com.google.code.gson" name="gson" rev="2.8.1"/>
|
||||
|
||||
<!-- for viewers -->
|
||||
<dependency conf="autopsy_core->*" org="org.freedesktop.gstreamer" name="gst1-java-core" rev="0.9.3"/>
|
||||
<dependency conf="autopsy_core->*" org="net.java.dev.jna" name="jna" rev="3.4.0"/>
|
||||
<dependency conf="autopsy_core->*" org="net.java.dev.jna" name="platform" rev="3.4.0"/>
|
||||
<dependency conf="autopsy_core->*" org="com.googlecode.gstreamer-java" name="gstreamer-java" rev="1.5"/>
|
||||
|
||||
<!-- for file search -->
|
||||
<dependency conf="autopsy_core->*" org="com.github.lgooddatepicker" name="LGoodDatePicker" rev="10.3.1"/>
|
||||
@ -69,9 +67,5 @@
|
||||
<dependency conf="autopsy_core->default" org="com.twelvemonkeys.imageio" name="imageio-thumbsdb" rev="3.2" />
|
||||
<dependency conf="autopsy_core->default" org="com.twelvemonkeys.imageio" name="imageio-core" rev="3.2" />
|
||||
<dependency conf="autopsy_core->default" org="com.twelvemonkeys.imageio" name="imageio-metadata" rev="3.2" />
|
||||
|
||||
<!-- conflict resolutions for multiple JAR versions -->
|
||||
<conflict org="net.java.dev.jna" module="jna" rev="3.4.0"/>
|
||||
<conflict org="net.java.dev.jna" module="platform" rev="3.4.0"/>
|
||||
</dependencies>
|
||||
</ivy-module>
|
||||
|
@ -24,8 +24,6 @@ file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar
|
||||
file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar
|
||||
file.reference.gson-2.8.1.jar=release/modules/ext/gson-2.8.1.jar
|
||||
file.reference.gstreamer-java-1.5.jar=release/modules/ext/gstreamer-java-1.5.jar
|
||||
file.reference.gst1-java-core-0.9.3.jar=release/modules/ext/gst1-java-core-0.9.3.jar
|
||||
file.reference.jna-3.4.0.jar=release/modules/ext/jna-3.4.0.jar
|
||||
file.reference.guava-19.0.jar=release/modules/ext/guava-19.0.jar
|
||||
file.reference.imageio-bmp-3.2.jar=release/modules/ext/imageio-bmp-3.2.jar
|
||||
file.reference.imageio-core-3.2.jar=release/modules/ext/imageio-core-3.2.jar
|
||||
|
@ -587,24 +587,24 @@
|
||||
<package>org.dom4j.util</package>
|
||||
<package>org.dom4j.xpath</package>
|
||||
<package>org.dom4j.xpp</package>
|
||||
<package>org.freedesktop.gstreamer</package>
|
||||
<package>org.freedesktop.gstreamer.controller</package>
|
||||
<package>org.freedesktop.gstreamer.elements</package>
|
||||
<package>org.freedesktop.gstreamer.elements.good</package>
|
||||
<package>org.freedesktop.gstreamer.event</package>
|
||||
<package>org.freedesktop.gstreamer.example</package>
|
||||
<package>org.freedesktop.gstreamer.glib</package>
|
||||
<package>org.freedesktop.gstreamer.interfaces</package>
|
||||
<package>org.freedesktop.gstreamer.io</package>
|
||||
<package>org.freedesktop.gstreamer.lowlevel</package>
|
||||
<package>org.freedesktop.gstreamer.lowlevel.annotations</package>
|
||||
<package>org.freedesktop.gstreamer.media</package>
|
||||
<package>org.freedesktop.gstreamer.media.event</package>
|
||||
<package>org.freedesktop.gstreamer.message</package>
|
||||
<package>org.freedesktop.gstreamer.query</package>
|
||||
<package>org.freedesktop.gstreamer.swing</package>
|
||||
<package>org.freedesktop.gstreamer.swt</package>
|
||||
<package>org.freedesktop.gstreamer.swt.overlay</package>
|
||||
<package>org.gstreamer</package>
|
||||
<package>org.gstreamer.controller</package>
|
||||
<package>org.gstreamer.elements</package>
|
||||
<package>org.gstreamer.elements.good</package>
|
||||
<package>org.gstreamer.event</package>
|
||||
<package>org.gstreamer.example</package>
|
||||
<package>org.gstreamer.glib</package>
|
||||
<package>org.gstreamer.interfaces</package>
|
||||
<package>org.gstreamer.io</package>
|
||||
<package>org.gstreamer.lowlevel</package>
|
||||
<package>org.gstreamer.lowlevel.annotations</package>
|
||||
<package>org.gstreamer.media</package>
|
||||
<package>org.gstreamer.media.event</package>
|
||||
<package>org.gstreamer.message</package>
|
||||
<package>org.gstreamer.query</package>
|
||||
<package>org.gstreamer.swing</package>
|
||||
<package>org.gstreamer.swt</package>
|
||||
<package>org.gstreamer.swt.overlay</package>
|
||||
<package>org.hyperic.jni</package>
|
||||
<package>org.hyperic.sigar</package>
|
||||
<package>org.hyperic.sigar.cmd</package>
|
||||
@ -967,8 +967,8 @@
|
||||
<binary-origin>release/modules/ext/common-lang-3.2.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/gst1-java-core-0.9.3.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/gst1-java-core-0.9.3.jar</binary-origin>
|
||||
<runtime-relative-path>ext/gstreamer-java-1.5.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/gstreamer-java-1.5.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/dom4j-1.6.1.jar</runtime-relative-path>
|
||||
|
@ -21,10 +21,10 @@
|
||||
<if>
|
||||
<isset property="jre.home.32" />
|
||||
<then>
|
||||
<echo message="32-bit JRE found, 32-bit installer will be built."/>
|
||||
<echo message="32-bit JRE found, 32 bit installer will be built."/>
|
||||
</then>
|
||||
<else>
|
||||
<echo message="32-bit JRE not found. No 32-bit installer will be built. Set the JRE_HOME_32 environment variable to generate a 32-bit installer."/>
|
||||
<echo message="32-bit JRE not found. No 32 bit installer will be build. Set the JRE_HOME_32 environment variable to generate a 32-bit installer."/>
|
||||
</else>
|
||||
</if>
|
||||
|
||||
@ -34,10 +34,10 @@
|
||||
<if>
|
||||
<isset property="jre.home.64" />
|
||||
<then>
|
||||
<echo message="64-bit JRE found, 64-bit installer will be built."/>
|
||||
<echo message="64-bit JRE found, 64 bit installer will be built."/>
|
||||
</then>
|
||||
<else>
|
||||
<echo message="64-bit JRE not found. No 64-bit installer will be built. Set the JRE_HOME_64 environment variable to generate a 64-bit installer."/>
|
||||
<echo message="64-bit JRE not found. No 64 bit installer will be build. Set the JRE_HOME_64 environment variable to generate a 64-bit installer."/>
|
||||
</else>
|
||||
</if>
|
||||
</target>
|
||||
@ -118,15 +118,19 @@
|
||||
<arg line="/edit ${aip-path-base} /SetAppdir -buildname DefaultBuild -path [ProgramFilesFolder][ProductName]-${app.version}"/>
|
||||
</exec>
|
||||
|
||||
<var name="gstreamer-relative-path" value="gstreamer/1.0/x86"/>
|
||||
<antcall target="install-gstreamer" inheritAll="true" />
|
||||
<!-- gstreamer needs special path info to be set -->
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\lib\gstreamer-0.10 -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name PATH -value %GSTREAMER_PATH% -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
|
||||
<antcall target="ai-build" inheritAll="true" />
|
||||
|
||||
<delete includeEmptyDirs="true">
|
||||
<fileset dir="${inst-path}/${app.name}/${gstreamer-relative-path}"/>
|
||||
</delete>
|
||||
|
||||
<delete dir="${nbdist.dir}/installer_${app.name}_32-cache"/>
|
||||
<move file="${nbdist.dir}/installer_${app.name}_32-SetupFiles/installer_${app.name}_32.msi" tofile="${nbdist.dir}/${app.name}-${app.version}-32bit.msi" />
|
||||
</target>
|
||||
@ -154,47 +158,13 @@
|
||||
<arg line="/edit ${aip-path} /SetPackageType x64"/>
|
||||
</exec>
|
||||
|
||||
<var name="gstreamer-relative-path" value="gstreamer/1.0/x86_64"/>
|
||||
<antcall target="install-gstreamer" inheritAll="true" />
|
||||
|
||||
<antcall target="ai-build" inheritAll="true" />
|
||||
|
||||
<delete includeEmptyDirs="true">
|
||||
<fileset dir="${inst-path}/${app.name}/${gstreamer-relative-path}"/>
|
||||
</delete>
|
||||
|
||||
<delete dir="${nbdist.dir}/installer_${app.name}_64-cache"/>
|
||||
<move file="${nbdist.dir}/installer_${app.name}_64-SetupFiles/installer_${app.name}_64.msi" tofile="${nbdist.dir}/${app.name}-${app.version}-64bit.msi" />
|
||||
</target>
|
||||
|
||||
|
||||
<target name="install-gstreamer" description="Install a copy of GStreamer.">
|
||||
<!-- Make GStreamer available to the installer -->
|
||||
<mkdir dir="${inst-path}/${app.name}/${gstreamer-relative-path}"/>
|
||||
<copy todir="${inst-path}/${app.name}/${gstreamer-relative-path}" >
|
||||
<fileset dir="${thirdparty.dir}/${gstreamer-relative-path}"/>
|
||||
</copy>
|
||||
|
||||
<!-- The 'libgstlibav.dll' file is too big to store on GitHub, so we
|
||||
have it stored in a ZIP file. We'll extract it in place and remove
|
||||
the ZIP file afterward. -->
|
||||
<unzip src="${inst-path}/${app.name}/${gstreamer-relative-path}/lib/gstreamer-1.0/libgstlibav.zip"
|
||||
dest="${inst-path}/${app.name}/${gstreamer-relative-path}/lib/gstreamer-1.0/"/>
|
||||
<delete file="${inst-path}/${app.name}/${gstreamer-relative-path}/lib/gstreamer-1.0/libgstlibav.zip" />
|
||||
|
||||
<!-- GStreamer needs special path info to be set -->
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]${app.name}\${gstreamer-relative-path}\bin -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]${app.name}\${gstreamer-relative-path}\lib\gstreamer-1.0 -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name PATH -value %GSTREAMER_PATH% -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
|
||||
<!-- 32/64 specific since config settings are different -->
|
||||
<target name="update-config" description="Updates configuration file with correct JVM args.">
|
||||
<!-- Update configuration file to include jre -->
|
||||
@ -224,7 +194,7 @@
|
||||
</path>
|
||||
</foreach>
|
||||
|
||||
<echo message="Removing extra Autopsy executable..."/>
|
||||
<echo message="Removing extra executable..."/>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /DelFile APPDIR\bin\${aut-bin-name-todelete}"/>
|
||||
</exec>
|
||||
|
@ -102,6 +102,7 @@
|
||||
<copy file="${basedir}/NEWS.txt" tofile="${zip-tmp}/${app.name}/NEWS.txt"/>
|
||||
<copy file="${basedir}/Running_Linux_OSX.txt" tofile="${zip-tmp}/${app.name}/Running_Linux_OSX.txt"/>
|
||||
<copy file="${basedir}/unix_setup.sh" tofile="${zip-tmp}/${app.name}/unix_setup.sh"/>
|
||||
<unzip src="${thirdparty.dir}/gstreamer/windows/i386/0.10.7/gstreamer.zip" dest="${zip-tmp}/${app.name}/gstreamer"/>
|
||||
|
||||
|
||||
<copy file="${basedir}/icons/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/>
|
||||
|
BIN
thirdparty/gstreamer/1.0/x86/bin/gdbus.exe
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/gdbus.exe
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/ges-launch-1.0.exe
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/ges-launch-1.0.exe
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/gsettings.exe
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/gsettings.exe
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/gst-inspect-1.0.exe
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/gst-inspect-1.0.exe
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/gst-launch-1.0.exe
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/gst-launch-1.0.exe
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/gst-play-1.0.exe
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/gst-play-1.0.exe
vendored
Binary file not shown.
24
thirdparty/gstreamer/1.0/x86/bin/gst-shell
vendored
24
thirdparty/gstreamer/1.0/x86/bin/gst-shell
vendored
@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
export GSTREAMER_ROOT="c:/gstreamer/1.0/x86"
|
||||
export CPPFLAGS="-I${GSTREAMER_ROOT}/include ${CPPFLAGS}"
|
||||
export GST_REGISTRY_1_0="${HOME}/.cache/gstreamer-1.0/gstreamer-cerbero-registry"
|
||||
export XDG_CONFIG_DIRS="${GSTREAMER_ROOT}/etc/xdg${XDG_CONFIG_DIRS:+:$XDG_CONFIG_DIRS}:/etc/xdg"
|
||||
export LDFLAGS="-L${GSTREAMER_ROOT}/lib ${LDFLAGS}"
|
||||
export XDG_DATA_DIRS="${GSTREAMER_ROOT}/share${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}:/usr/local/share:/usr/share"
|
||||
export GST_PLUGIN_SYSTEM_PATH_1_0="${GSTREAMER_ROOT}/lib/gstreamer-1.0"
|
||||
export GIO_EXTRA_MODULES="${GSTREAMER_ROOT}/lib/gio/modules"
|
||||
export GST_PLUGIN_SYSTEM_PATH="${GSTREAMER_ROOT}/lib/gstreamer-0.10"
|
||||
export GST_PLUGIN_SCANNER="${GSTREAMER_ROOT}/libexec/gstreamer-0.10/gst-plugin-scanner"
|
||||
export GST_PLUGIN_SCANNER_1_0="${GSTREAMER_ROOT}/libexec/gstreamer-1.0/gst-plugin-scanner"
|
||||
export CFLAGS="-I${GSTREAMER_ROOT}/include ${CFLAGS}"
|
||||
export PYTHONPATH="${GSTREAMER_ROOT}/lib/python2.7/site-packages${PYTHONPATH:+:$PYTHONPATH}"
|
||||
export PKG_CONFIG_PATH="${GSTREAMER_ROOT}/lib/pkgconfig:${GSTREAMER_ROOT}/share/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
|
||||
export PATH="${GSTREAMER_ROOT}/bin${PATH:+:$PATH}:/usr/local/bin:/usr/bin:/bin"
|
||||
export GST_REGISTRY="${HOME}/.gstreamer-0.10/gstreamer-cerbero-registry"
|
||||
export LD_LIBRARY_PATH="${GSTREAMER_ROOT}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
export CXXFLAGS="-I${GSTREAMER_ROOT}/include ${CXXFLAGS}"
|
||||
export GI_TYPELIB_PATH="${GSTREAMER_ROOT}/lib/girepository-1.0"
|
||||
|
||||
|
||||
$SHELL "$@"
|
Binary file not shown.
Binary file not shown.
@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2014,Thibault Saunier <thibault.saunier@collabora.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
LIBDIR = r'/c/gstreamer/1.0/x86/lib'
|
||||
BUILDDIR = r'@BUILDDIR@'
|
||||
SRCDIR = r'@SRCDIR@'
|
||||
GIT_FIRST_HASH = 'da962d096af9460502843e41b7d25fdece7ff1c2'
|
||||
|
||||
|
||||
def _get_git_first_hash(path):
|
||||
cdir = os.path.abspath(os.curdir)
|
||||
try:
|
||||
os.chdir(path)
|
||||
res = subprocess.check_output(['git', 'rev-list', '--max-parents=0', 'HEAD']).decode().rstrip('\n')
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
res = ''
|
||||
finally:
|
||||
os.chdir(cdir)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _in_devel():
|
||||
root_dir = os.path.abspath(os.path.dirname(os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"..", "..", "..")))
|
||||
return _get_git_first_hash(root_dir) == GIT_FIRST_HASH
|
||||
|
||||
|
||||
def _add_gst_launcher_path():
|
||||
f = os.path.abspath(__file__)
|
||||
if _in_devel():
|
||||
print("Running with development path")
|
||||
dir_ = os.path.dirname(os.path.abspath(__file__))
|
||||
root = os.path.split(dir_)[0]
|
||||
elif f.startswith(BUILDDIR):
|
||||
# Make sure to have the configured config.py in the python path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(BUILDDIR, "..")))
|
||||
root = os.path.abspath(os.path.join(SRCDIR, "../"))
|
||||
else:
|
||||
root = os.path.join(LIBDIR, 'gst-validate-launcher', 'python')
|
||||
|
||||
sys.path.insert(0, root)
|
||||
return os.path.join(root, "launcher")
|
||||
|
||||
|
||||
if "__main__" == __name__:
|
||||
libsdir = _add_gst_launcher_path()
|
||||
from launcher.main import main
|
||||
exit(main(libsdir))
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libFLAC-8.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libFLAC-8.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libSoundTouch-1.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libSoundTouch-1.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/liba52-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/liba52-0.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libass-9.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libass-9.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libbz2.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libbz2.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libcairo-2.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libcairo-2.dll
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libcharset-1.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libcharset-1.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libcroco-0.6-3.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libcroco-0.6-3.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libcrypto-1_1.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libcrypto-1_1.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libdca-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libdca-0.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libdv-4.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libdv-4.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libdvdnav-4.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libdvdnav-4.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libdvdread-4.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libdvdread-4.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libexpat-1.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libexpat-1.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libfaad-2.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libfaad-2.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libffi-7.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libffi-7.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libfontconfig-1.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libfontconfig-1.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libfreetype-6.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libfreetype-6.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libfribidi-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libfribidi-0.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgcc_s_sjlj-1.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgcc_s_sjlj-1.dll
vendored
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libges-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libges-1.0-0.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgio-2.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgio-2.0-0.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libglib-2.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libglib-2.0-0.dll
vendored
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgmp-10.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgmp-10.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgnutls-30.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgnutls-30.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgnutlsxx-28.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgnutlsxx-28.dll
vendored
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgomp-1.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgomp-1.dll
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstapp-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstapp-1.0-0.dll
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstfft-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstfft-1.0-0.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstgl-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstgl-1.0-0.dll
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstnet-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstnet-1.0-0.dll
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstrtp-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstrtp-1.0-0.dll
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstsdp-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgstsdp-1.0-0.dll
vendored
Binary file not shown.
BIN
thirdparty/gstreamer/1.0/x86/bin/libgsttag-1.0-0.dll
vendored
BIN
thirdparty/gstreamer/1.0/x86/bin/libgsttag-1.0-0.dll
vendored
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user