diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 2406271416..ba4dc45c96 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -52,6 +52,7 @@ file.reference.sis-metadata-0.6.jar=release/modules/ext/sis-metadata-0.6.jar file.reference.sis-netcdf-0.6.jar=release/modules/ext/sis-netcdf-0.6.jar file.reference.sis-utility-0.6.jar=release/modules/ext/sis-utility-0.6.jar file.reference.slf4j-api-1.7.24.jar=release/modules/ext/slf4j-api-1.7.24.jar +file.reference.sqlite-jdbc-3.25.2.jar=release/modules/ext/sqlite-jdbc-3.25.2.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar file.reference.jempbox-1.8.13.jar=release/modules/ext/jempbox-1.8.13.jar @@ -77,6 +78,7 @@ 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 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 37ea029fba..faf08934d4 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -415,6 +415,10 @@ ext/StixLib.jar release/modules/ext/StixLib.jar + + ext/jackson-core-2.9.7.jar + release/modules/ext/jackson-core-2.9.7.jar + ext/pdfbox-tools-2.0.8.jar release/modules/ext/pdfbox-tools-2.0.8.jar @@ -431,10 +435,6 @@ ext/tika-parsers-1.17.jar release/modules/ext/tika-parsers-1.17.jar - - ext/sqlite-jdbc-3.25.2.jar - release/modules/ext/sqlite-jdbc-3.25.2.jar - ext/json-simple-1.1.1.jar release/modules/ext/json-simple-1.1.1.jar @@ -447,6 +447,10 @@ ext/jhighlight-1.0.2.jar release/modules/ext/jhighlight-1.0.2.jar + + ext/sleuthkit-postgresql-4.6.5.jar + release/modules/ext/sleuthkit-postgresql-4.6.5.jar + ext/jempbox-1.8.13.jar release/modules/ext/jempbox-1.8.13.jar @@ -499,10 +503,6 @@ ext/isoparser-1.1.18.jar release/modules/ext/isoparser-1.1.18.jar - - ext/sleuthkit-postgresql-4.6.5.jar - release/modules/ext/sleuthkit-postgresql-4.6.5.jar - ext/vorbis-java-core-0.8.jar release/modules/ext/vorbis-java-core-0.8.jar @@ -608,8 +608,8 @@ release/modules/ext/curator-client-2.8.0.jar - ext/jackson-core-2.9.7.jar - release/modules/ext/jackson-core-2.9.7.jar + ext/sqlite-jdbc-3.25.2.jar + release/modules/ext/sqlite-jdbc-3.25.2.jar ext/cxf-rt-frontend-jaxrs-3.0.16.jar @@ -619,10 +619,6 @@ ext/grib-4.5.5.jar release/modules/ext/grib-4.5.5.jar - - ext/jackson-core-2.9.2.jar - release/modules/ext/jackson-core-2.9.2.jar - ext/activemq-all-5.11.1.jar release/modules/ext/activemq-all-5.11.1.jar diff --git a/Core/src/org/freedesktop/gstreamer/examples/SimpleVideoComponent.java b/Core/src/org/freedesktop/gstreamer/examples/SimpleVideoComponent.java new file mode 100755 index 0000000000..667c46e3d9 --- /dev/null +++ b/Core/src/org/freedesktop/gstreamer/examples/SimpleVideoComponent.java @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2018 Neil C Smith + * Copyright (c) 2007 Wayne Meissner + * + * This file is part of gstreamer-java. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with this work. If not, see . + */ +package org.freedesktop.gstreamer.examples; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.awt.image.VolatileImage; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.nio.IntBuffer; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import javax.swing.SwingUtilities; +import javax.swing.Timer; + +import org.freedesktop.gstreamer.Element; +import org.freedesktop.gstreamer.Structure; +import org.freedesktop.gstreamer.elements.AppSink; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import org.freedesktop.gstreamer.Buffer; +import org.freedesktop.gstreamer.Caps; +import org.freedesktop.gstreamer.FlowReturn; +import org.freedesktop.gstreamer.Sample; + +/** + * + */ +//DLG: Made public +public class SimpleVideoComponent extends javax.swing.JComponent { + + private BufferedImage currentImage = null; + private final Lock bufferLock = new ReentrantLock(); + private final AppSink videosink; +// private Pad videoPad; + private RenderComponent renderComponent = new RenderComponent(); + private boolean keepAspect = true; + private Timer resourceTimer; + private VolatileImage volatileImage; + private boolean frameRendered = false; + private volatile boolean updatePending = false; + private final boolean useVolatile; + + /** + * Creates a new instance of GstVideoComponent + */ + public SimpleVideoComponent() { + this(new AppSink("GstVideoComponent")); + } + + /** + * Creates a new instance of GstVideoComponent + */ + public SimpleVideoComponent(AppSink appsink) { + this.videosink = appsink; + videosink.set("emit-signals", true); + AppSinkListener listener = new AppSinkListener(); + videosink.connect((AppSink.NEW_SAMPLE) listener); + videosink.connect((AppSink.NEW_PREROLL) listener); + StringBuilder caps = new StringBuilder("video/x-raw,pixel-aspect-ratio=1/1,"); + // JNA creates ByteBuffer using native byte order, set masks according to that. + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + caps.append("format=BGRx"); + } else { + caps.append("format=xRGB"); + } + videosink.setCaps(new Caps(caps.toString())); + + useVolatile = true; + + // Kick off a timer to free up the volatile image if there have been no recent updates + // (e.g. the player is paused) + // + resourceTimer = new Timer(250, resourceReaper); + + // + // Don't use a layout manager - the output component will positioned within this + // component according to the aspect ratio and scaling mode + // + setLayout(null); + add(renderComponent); + + // + // Listen for the child changing its preferred size to the size of the + // video stream. + // + renderComponent.addPropertyChangeListener("preferredSize", new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + setPreferredSize(renderComponent.getPreferredSize()); + scaleVideoOutput(); + } + }); + // + // Scale the video output in response to this component being resized + // + addComponentListener(new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent arg0) { + scaleVideoOutput(); + } + + }); + renderComponent.setBounds(getBounds()); + setOpaque(true); + setBackground(Color.BLACK); + } + + /** + * Scales the video output component according to its aspect ratio + */ + private void scaleVideoOutput() { + final Component child = renderComponent; + final Dimension childSize = child.getPreferredSize(); + final int width = getWidth(), height = getHeight(); + // Figure out the aspect ratio + double aspect = keepAspect ? (double) childSize.width / (double) childSize.height : 1.0f; + + // + // Now scale and position the videoChild component to be in the correct position + // to keep the aspect ratio correct. + // + int scaledHeight = (int) ((double) width / aspect); + if (!keepAspect) { + // + // Just make the child match the parent + // + child.setBounds(0, 0, width, height); + } else if (scaledHeight < height) { + // + // Output window is taller than the image is when scaled, so move the + // video component to sit vertically in the centre of the VideoComponent. + // + final int y = (height - scaledHeight) / 2; + child.setBounds(0, y, width, scaledHeight); + } else { + final int scaledWidth = (int) ((double) height * aspect); + final int x = (width - scaledWidth) / 2; + child.setBounds(x, 0, scaledWidth, height); + } + } + private ActionListener resourceReaper = new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + if (!frameRendered) { + if (volatileImage != null) { + volatileImage.flush(); + volatileImage = null; + } + + // Stop the timer so we don't wakeup needlessly + resourceTimer.stop(); + } + frameRendered = false; + } + }; + + public Element getElement() { + return videosink; + } + + public void setKeepAspect(boolean keepAspect) { + this.keepAspect = keepAspect; + } + + @Override + public boolean isLightweight() { + return true; + } + + @Override + protected void paintComponent(Graphics g) { + if (isOpaque()) { + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setColor(getBackground()); + g2d.fillRect(0, 0, getWidth(), getHeight()); + g2d.dispose(); + } + } + + private class RenderComponent extends javax.swing.JComponent { + + private static final long serialVersionUID = -4736605073704494268L; + + @Override + protected void paintComponent(Graphics g) { + int width = getWidth(), height = getHeight(); + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + if (currentImage != null) { + GraphicsConfiguration gc = getGraphicsConfiguration(); + render(g2d, 0, 0, width, height); + } else { + g2d.setColor(getBackground()); + g2d.fillRect(0, 0, width, height); + } + g2d.dispose(); + } + + @Override + public boolean isOpaque() { + return SimpleVideoComponent.this.isOpaque(); + } + + @Override + public boolean isLightweight() { + return true; + } + } + + private void renderVolatileImage(BufferedImage bufferedImage) { + do { + int w = bufferedImage.getWidth(), h = bufferedImage.getHeight(); + GraphicsConfiguration gc = getGraphicsConfiguration(); + if (volatileImage == null || volatileImage.getWidth() != w + || volatileImage.getHeight() != h + || volatileImage.validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE) { + if (volatileImage != null) { + volatileImage.flush(); + } + volatileImage = gc.createCompatibleVolatileImage(w, h); + volatileImage.setAccelerationPriority(1.0f); + } + // + // Now paint the BufferedImage into the accelerated image + // + Graphics2D g = volatileImage.createGraphics(); + g.drawImage(bufferedImage, 0, 0, null); + g.dispose(); + } while (volatileImage.contentsLost()); + } + + /** + * Renders to a volatile image, and then paints that to the screen. This + * helps with scaling performance on accelerated surfaces (e.g. OpenGL) + * + * @param g the graphics to paint the image to + * @param x the left coordinate to start painting at. + * @param y the top coordinate to start painting at. + * @param w the width of the paint area + * @param h the height of the paint area + */ + private void volatileRender(Graphics g, int x, int y, int w, int h) { + do { + if (updatePending || volatileImage == null + || volatileImage.validate(getGraphicsConfiguration()) != VolatileImage.IMAGE_OK) { + bufferLock.lock(); + try { + updatePending = false; + renderVolatileImage(currentImage); + } finally { + bufferLock.unlock(); + } + } + g.drawImage(volatileImage, x, y, w, h, null); + } while (volatileImage.contentsLost()); + } + + /** + * Renders directly to the given Graphics. This is only really + * useful on MacOS where swing graphics are unaccelerated so using a + * volatile just incurs an extra memcpy(). + * + * @param g the graphics to paint the image to + * @param x the left coordinate to start painting at. + * @param y the top coordinate to start painting at. + * @param w the width of the paint area + * @param h the height of the paint area + */ + private void heapRender(Graphics g, int x, int y, int w, int h) { + bufferLock.lock(); + try { + updatePending = false; + g.drawImage(currentImage, x, y, w, h, null); + } finally { + bufferLock.unlock(); + } + } + + /** + * Renders the current frame to the given Graphics. + * + * @param g the graphics to paint the image to + * @param x the left coordinate to start painting at. + * @param y the top coordinate to start painting at. + * @param w the width of the paint area + * @param h the height of the paint area + */ + private void render(Graphics g, int x, int y, int w, int h) { + if (useVolatile) { + volatileRender(g, x, y, w, h); + } else { + heapRender(g, x, y, w, h); + } + // + // Restart the resource reaper timer if neccessary + // + if (!frameRendered) { + frameRendered = true; + if (!resourceTimer.isRunning()) { + resourceTimer.restart(); + } + } + } + + private int imgWidth = 0, imgHeight = 0; + + private final void update(final int width, final int height) { + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + // + // If the image changed size, resize the component to fit + // + if (width != imgWidth || height != imgHeight) { + renderComponent.setPreferredSize(new Dimension(width, height)); + imgWidth = width; + imgHeight = height; + } + + if (renderComponent.isVisible()) { + renderComponent.paintImmediately(0, 0, + renderComponent.getWidth(), renderComponent.getHeight()); + } + } + }); + } + + private BufferedImage getBufferedImage(int width, int height) { + if (currentImage != null && currentImage.getWidth() == width + && currentImage.getHeight() == height) { + return currentImage; + } + if (currentImage != null) { + currentImage.flush(); + } + currentImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + currentImage.setAccelerationPriority(0.0f); + return currentImage; + } + + private class AppSinkListener implements AppSink.NEW_SAMPLE, AppSink.NEW_PREROLL { + + public void rgbFrame(boolean isPrerollFrame, int width, int height, IntBuffer rgb) { + // If the EDT is still copying data from the buffer, just drop this frame + // + if (!bufferLock.tryLock()) { + return; + } + + // + // If there is already a swing update pending, also drop this frame. + // + if (updatePending && !isPrerollFrame) { + bufferLock.unlock(); + return; + } + try { + final BufferedImage renderImage = getBufferedImage(width, height); + int[] pixels = ((DataBufferInt) renderImage.getRaster().getDataBuffer()).getData(); + rgb.get(pixels, 0, width * height); + updatePending = true; + } finally { + bufferLock.unlock(); + } + +// int scaledWidth = currentImage.getWidth(); +// if (keepAspect) { +// // Scale width according to pixel aspect ratio. +// Caps videoCaps = videoPad.getNegotiatedCaps(); +// Structure capsStruct = videoCaps.getStructure(0); +// if (capsStruct.hasField("pixel-aspect-ratio")) { +// Fraction pixelAspectRatio = capsStruct.getFraction("pixel-aspect-ratio"); +// scaledWidth = scaledWidth * pixelAspectRatio.getNumerator() / pixelAspectRatio.getDenominator(); +// } +// } + // Tell swing to use the new buffer + update(currentImage.getWidth(), currentImage.getHeight()); + } + + @Override + public FlowReturn newSample(AppSink elem) { + Sample sample = elem.pullSample(); + Structure capsStruct = sample.getCaps().getStructure(0); + int w = capsStruct.getInteger("width"); + int h = capsStruct.getInteger("height"); + Buffer buffer = sample.getBuffer(); + ByteBuffer bb = buffer.map(false); + if (bb != null) { + rgbFrame(false, w, h, bb.asIntBuffer()); + buffer.unmap(); + } + sample.dispose(); + return FlowReturn.OK; + } + + @Override + public FlowReturn newPreroll(AppSink elem) { + Sample sample = elem.pullPreroll(); + Structure capsStruct = sample.getCaps().getStructure(0); + int w = capsStruct.getInteger("width"); + int h = capsStruct.getInteger("height"); + Buffer buffer = sample.getBuffer(); + ByteBuffer bb = buffer.map(false); + if (bb != null) { + rgbFrame(false, w, h, bb.asIntBuffer()); + buffer.unmap(); + } + sample.dispose(); + return FlowReturn.OK; + } + + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java index 07736120c7..865123d4ab 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java @@ -25,6 +25,8 @@ import java.io.IOException; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.CancellationException; import java.util.logging.Level; import javafx.application.Platform; @@ -66,6 +68,7 @@ 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.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -73,11 +76,8 @@ 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 { +public class FXVideoPanel extends JPanel implements MediaFileViewer.MediaViewPanel { // Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html // for Javafx supported formats @@ -109,8 +109,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { return this; } - @Override - void setupVideo(final AbstractFile file, final Dimension dims) { + void loadFile(final AbstractFile file, final Dimension dims) { if (file.equals(currentFile)) { return; } @@ -150,7 +149,6 @@ public class FXVideoPanel extends MediaViewVideoPanel { } - @Override void reset() { Platform.runLater(() -> { if (mediaPane != null) { @@ -199,7 +197,6 @@ public class FXVideoPanel extends MediaViewVideoPanel { private javafx.embed.swing.JFXPanel jFXPanel; // End of variables declaration//GEN-END:variables - @Override public boolean isInited() { return fxInited; } @@ -641,29 +638,49 @@ public class FXVideoPanel extends MediaViewVideoPanel { } } - /** - * @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 captureFrames(java.io.File file, int numFrames) throws Exception { - //What is/was the point of this method /interface. - return null; + public List getSupportedExtensions() { + return Arrays.asList(EXTENSIONS.clone()); } @Override - public String[] getExtensions() { - return EXTENSIONS.clone(); - } - - @Override - public List getMimeTypes() { + public List getSupportedMimeTypes() { return MIMETYPES; } + + @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 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; + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoRendererPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoRendererPanel.java new file mode 100755 index 0000000000..16f2e99854 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoRendererPanel.java @@ -0,0 +1,156 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +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. + */ +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; + private BorderPane borderpane; + + /** + * 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 = 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 img = new WritableImage(width, height); + PixelWriter pw = img.getPixelWriter(); + pw.setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), pixels, 0, width * 4); + return img; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java index 963e3f8b39..0085a2d4e8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java @@ -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 MediaViewVideoPanel videoPanel; - private final boolean videoPanelInited; + private final MediaPlayerPanel mediaPlayerPanel; + private final boolean mediaPlayerPanelInited; private final MediaViewImagePanel imagePanel; private final boolean imagePanelInited; private static final String IMAGE_VIEWER_LAYER = "IMAGE"; //NON-NLS - private static final String VIDEO_VIEWER_LAYER = "VIDEO"; //NON-NLS + private static final String MEDIA_PLAYER_LAYER = "AUDIO_VIDEO"; //NON-NLS /** - * Creates new form DataContentViewerVideo + * Creates a new MediaFileViewer. */ public MediaFileViewer() { initComponents(); // get the right panel for our platform - videoPanel = MediaViewVideoPanel.createVideoPanel(); - videoPanelInited = videoPanel.isInited(); + mediaPlayerPanel = new MediaPlayerPanel(); + mediaPlayerPanelInited = mediaPlayerPanel.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(videoPanel, VIDEO_VIEWER_LAYER); + add(mediaPlayerPanel, MEDIA_PLAYER_LAYER); - showVideoPanel(false); + showImagePanel(); } /** @@ -94,8 +94,8 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { List mimeTypes = new ArrayList<>(); - mimeTypes.addAll(this.imagePanel.getMimeTypes()); - mimeTypes.addAll(this.videoPanel.getMimeTypes()); + mimeTypes.addAll(this.imagePanel.getSupportedMimeTypes()); + mimeTypes.addAll(this.mediaPlayerPanel.getSupportedMimeTypes()); 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 (videoPanelInited && videoPanel.isSupported(file)) { - videoPanel.setupVideo(file, dims); - this.showVideoPanel(true); + if (mediaPlayerPanelInited && mediaPlayerPanel.isSupported(file)) { + mediaPlayerPanel.loadFile(file, dims); + this.showVideoPanel(); } else if (imagePanelInited && imagePanel.isSupported(file)) { imagePanel.showImageFx(file, dims); - this.showVideoPanel(false); + this.showImagePanel(); } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Exception while setting node", e); //NON-NLS @@ -136,17 +136,19 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { } /** - * switch to visible video or image panel - * - * @param showVideo true if video panel, false if image panel + * Show the media player panel. */ - private void showVideoPanel(boolean showVideo) { + private void showVideoPanel() { CardLayout layout = (CardLayout) this.getLayout(); - if (showVideo) { - layout.show(this, VIDEO_VIEWER_LAYER); - } else { - layout.show(this, IMAGE_VIEWER_LAYER); - } + layout.show(this, MEDIA_PLAYER_LAYER); + } + + /** + * Show the image panel. + */ + private void showImagePanel() { + CardLayout layout = (CardLayout) this.getLayout(); + layout.show(this, IMAGE_VIEWER_LAYER); } @Override @@ -156,24 +158,24 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public void resetComponent() { - videoPanel.reset(); + mediaPlayerPanel.reset(); imagePanel.reset(); lastFile = null; } - interface MediaViewPanel { + protected interface MediaViewPanel { /** * @return supported mime types */ - List getMimeTypes(); + List getSupportedMimeTypes(); /** * returns supported extensions (each starting with .) * * @return */ - List getExtensionsList(); + List getSupportedExtensions(); boolean isSupported(AbstractFile file); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form similarity index 100% rename from Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.form rename to Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java similarity index 65% rename from Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index a1ca715a19..4522c406b4 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,20 +20,19 @@ 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.awt.EventQueue; 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.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; @@ -43,14 +42,18 @@ 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.freedesktop.gstreamer.Bus; +import org.freedesktop.gstreamer.ClockTime; +import org.freedesktop.gstreamer.Format; +import org.freedesktop.gstreamer.Gst; +import org.freedesktop.gstreamer.GstException; +import org.freedesktop.gstreamer.GstObject; +import org.freedesktop.gstreamer.Message; +import org.freedesktop.gstreamer.MessageType; +import org.freedesktop.gstreamer.State; +import org.freedesktop.gstreamer.StateChangeReturn; +import org.freedesktop.gstreamer.Structure; +import org.freedesktop.gstreamer.elements.PlayBin; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; @@ -62,39 +65,133 @@ 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; -@ServiceProviders(value = { - @ServiceProvider(service = FrameCapture.class) -}) @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public class GstVideoPanel extends MediaViewVideoPanel { +public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel { - private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS - private static final List 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 String[] EXTENSIONS = new String[] { + ".3gp", + ".aac", //froze + ".aif", + ".aiff", + ".amr", + ".asf", //froze + ".au", + ".avi", + ".flac", + ".flv", + ".m4a", + ".m4v", + ".mka", + ".mkv", + ".mov", + ".mp2", //froze + ".mp3", //froze + ".mp4", + ".mpeg", + ".mpg", + ".mxf", + ".ogg", + ".ra", //froze + ".wav", + ".webm", + ".wma", + ".wmv", + }; //NON-NLS + private static final List MIMETYPES = Arrays.asList( + "video/3gpp", //tested + "audio/aiff", //tested + "audio/amr-wb", + "audio/basic", + "audio/mp4", //tested + "video/mp4", //tested + "audio/mpeg", //froze + "video/mpeg", //tested + "audio/mpeg3", + "application/mxf", //tested + "application/ogg", + "video/quicktime", //tested + "audio/vorbis", //tested + "application/vnd.rn-realmedia", + "audio/vnd.wave", //tested + "video/webm", //tested + "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", //tested + "video/x-flv", //tested + "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", //tested + "audio/x-mace", + "audio/x-matroska", //tested + "video/x-matroska", //tested + "audio/x-mpeg", + "video/x-mpeg", + "audio/x-mpeg-3", + "video/x-ms-asf", + "audio/x-ms-wma", //tested + "video/x-ms-wmv", //tested + "video/x-msmpeg", + "video/x-msvideo", //tested + "video/x-msvideocodec", + "audio/x-mulaw", + "audio/x-nist", + "audio/x-oggflac", //tested + "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(GstVideoPanel.class.getName()); + private static final Logger logger = Logger.getLogger(MediaPlayerPanel.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"); + private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(MediaPlayerPanel.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 volatile PlayBin gstPlayBin; + private GstVideoRendererPanel gstVideoRenderer; private boolean autoTracking = false; // true if the slider is moving automatically - private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player + private final Object playbinLock = new Object(); // lock for synchronization of gstPlayBin player private AbstractFile currentFile; - private final Set badVideoFiles = Collections.synchronizedSet(new HashSet<>()); /** * Creates new form MediaViewVideoPanel */ - public GstVideoPanel() { + public MediaPlayerPanel() { initComponents(); customizeComponents(); } @@ -115,11 +212,11 @@ public class GstVideoPanel extends MediaViewVideoPanel { return videoPanel; } - public VideoComponent getVideoComponent() { - return gstVideoComponent; - } - - @Override + /** + * Has this MediaPlayerPanel been initialized correctly? + * + * @return + */ public boolean isInited() { return gstInited; } @@ -140,19 +237,19 @@ public class GstVideoPanel extends MediaViewVideoPanel { */ 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 + if (gstPlayBin != null && !autoTracking) { + State orig = gstPlayBin.getState(); + if (gstPlayBin.pause() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.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 + if (gstPlayBin.seek(ClockTime.fromMillis(time)) == false) { + logger.log(Level.WARNING, "Attempt to call PlayBin.seek() failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); return; } - gstPlaybin2.setState(orig); + gstPlayBin.setState(orig); } } }); @@ -182,9 +279,14 @@ public class GstVideoPanel extends MediaViewVideoPanel { return true; } - @Override + /** + * 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 setupVideo(final AbstractFile file, final Dimension dims) { + void loadFile(final AbstractFile file, final Dimension dims) { reset(); infoLabel.setText(""); currentFile = file; @@ -221,32 +323,75 @@ public class GstVideoPanel extends MediaViewVideoPanel { progressSlider.setEnabled(true); - gstVideoComponent = new VideoComponent(); + //gstVideoComponent = new SimpleVideoComponent(); + gstVideoRenderer = new GstVideoRendererPanel(); synchronized (playbinLock) { - if (gstPlaybin2 != null) { - gstPlaybin2.dispose(); + if (gstPlayBin != null) { + gstPlayBin.dispose(); } - gstPlaybin2 = new PlayBin2("VideoPlayer"); //NON-NLS - gstPlaybin2.setVideoSink(gstVideoComponent.getElement()); + gstPlayBin = new PlayBin("VideoPlayer"); //NON-NLS + gstPlayBin.setVideoSink(gstVideoRenderer.getVideoSink()); videoPanel.removeAll(); videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(gstVideoComponent); + //videoPanel.add(gstVideoComponent); + + EventQueue.invokeLater(() -> { + videoPanel.add(gstVideoRenderer);//add jfx ui to JPanel + }); videoPanel.setVisible(true); - gstPlaybin2.setInputFile(ioFile); + gstPlayBin.setInputFile(ioFile); - if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS + if (gstPlayBin.setState(State.READY) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.setState(State.READY) failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); } + + gstPlayBin.getBus().connect(new Bus.EOS() { + @Override + public void endOfStream(GstObject source) { + long test = durationMillis; + System.out.println(test); + System.out.println(); + } + }); + gstPlayBin.getBus().connect(new Bus.ERROR() { + @Override + public void errorMessage(GstObject source, int code, String message) { + long test = durationMillis; + System.out.println(test); + System.out.println(); + } + }); + gstPlayBin.getBus().connect(new Bus.STATE_CHANGED() { + @Override + public void stateChanged(GstObject source, State old, State current, State pending) { + if (durationMillis == 0 && current.equals(State.PLAYING)) { + durationMillis = gstPlayBin.queryDuration().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; + } + long test = durationMillis; + System.out.println(test); + System.out.println(); + } + }); } } - @Override + /** + * Prepare this MediaViewVideoPanel to accept a different media file. + */ void reset() { // reset the progress label text on the event dispatch thread @@ -259,25 +404,26 @@ public class GstVideoPanel extends MediaViewVideoPanel { } 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 + if (gstPlayBin != null) { + if (gstPlayBin.isPlaying()) { + if (gstPlayBin.stop() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.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 + if (gstPlayBin.setState(State.NULL) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.setState(State.NULL) failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); return; } - if (gstPlaybin2.getState().equals(State.NULL)) { - gstPlaybin2.dispose(); + if (gstPlayBin.getState().equals(State.NULL)) { + gstPlayBin.dispose(); } - gstPlaybin2 = null; + gstPlayBin = null; } - gstVideoComponent = null; + //gstVideoComponent = null; + gstVideoRenderer = null; } // get rid of any existing videoProgressWorker thread @@ -289,152 +435,6 @@ public class GstVideoPanel extends MediaViewVideoPanel { 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 captureFrames(java.io.File file, int numFrames) throws Exception { - - List 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 @@ -462,16 +462,16 @@ public class GstVideoPanel extends MediaViewVideoPanel { .addGap(0, 231, Short.MAX_VALUE) ); - org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(GstVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N + 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(GstVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N + 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(GstVideoPanel.class, "MediaViewVideoPanel.infoLabel.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); @@ -523,30 +523,33 @@ public class GstVideoPanel extends MediaViewVideoPanel { private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed synchronized (playbinLock) { - State state = gstPlaybin2.getState(); + if (gstPlayBin == null) { + return; + } + State state = gstPlayBin.getState(); if (state.equals(State.PLAYING)) { - if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS + 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("►"); - // 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 + // Is this call necessary considering we just called gstPlayBin.pause()? + if (gstPlayBin.setState(State.PAUSED) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.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 + 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("||"); - // 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 + // Is this call necessary considering we just called gstPlayBin.play()? + if (gstPlayBin.setState(State.PLAYING) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.setState(State.PLAYING) failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); return; } @@ -584,23 +587,23 @@ public class GstVideoPanel extends MediaViewVideoPanel { private boolean isPlayBinReady() { synchronized (playbinLock) { - return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL); + return gstPlayBin != null && !gstPlayBin.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 + if (gstPlayBin != null) { + if (gstPlayBin.stop() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.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 + if (gstPlayBin.setState(State.READY) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.setState(State.READY) failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); } - gstPlaybin2.getState(); //NEW + gstPlayBin.getState(); //NEW } } pauseButton.setText("►"); @@ -631,7 +634,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { while (hasNotEnded() && isPlayBinReady() && !isCancelled()) { synchronized (playbinLock) { - pos = gstPlaybin2.queryPosition(); + pos = gstPlayBin.queryPosition(); } millisElapsed = pos.toMillis(); @@ -699,7 +702,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { @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)); + progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> ExtractMedia.this.cancel(true)); progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); progress.start(100); try { @@ -744,20 +747,26 @@ public class GstVideoPanel extends MediaViewVideoPanel { 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 + if (gstPlayBin.play() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.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 + /*gstPlayBin.getBus().connect(new Bus.DURATION() { + @Override + public void durationChanged(GstObject go, Format format, long l) { + System.out.println(); + } + });*/ + //DLG: + /*if (gstPlayBin.pause() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.pause() failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); return; - } - gstPlaybin2.getState(); - dur = gstPlaybin2.queryDuration(); + }*/ + State state = gstPlayBin.getState(); + dur = gstPlayBin.queryDuration(); } - durationMillis = dur.toMillis(); // pick out the total hours, minutes, seconds long durationSeconds = (int) durationMillis / 1000; @@ -771,12 +780,12 @@ public class GstVideoPanel extends MediaViewVideoPanel { 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 + /*synchronized (playbinLock) { + if (gstPlayBin.play() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin.play() failed."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); } - } + }*/ pauseButton.setText("||"); videoProgressWorker = new VideoProgressWorker(); videoProgressWorker.execute(); @@ -785,13 +794,49 @@ public class GstVideoPanel extends MediaViewVideoPanel { } @Override - public String[] getExtensions() { - return EXTENSIONS.clone(); + public List getSupportedExtensions() { + return Arrays.asList(EXTENSIONS.clone()); } @Override - public List getMimeTypes() { + public List getSupportedMimeTypes() { return MIMETYPES; } + @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 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; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index da526803a8..88ad22d1c1 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -230,7 +230,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan * @return supported mime types */ @Override - public List getMimeTypes() { + public List getSupportedMimeTypes() { return Collections.unmodifiableList(Lists.newArrayList(supportedMimes)); } @@ -240,7 +240,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan * @return */ @Override - public List getExtensionsList() { + public List getSupportedExtensions() { return getExtensions(); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewVideoPanel.java deleted file mode 100644 index 0a203a0909..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewVideoPanel.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-2018 Basis Technology Corp. - * Contact: carrier sleuthkit 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 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 getMimeTypes(); - - @Override - public List 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 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; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java deleted file mode 100755 index 4c49490373..0000000000 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit 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 { - -} diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index a5f7aab768..8ba1ccf7bc 100644 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -11,6 +11,8 @@ + + @@ -66,5 +68,8 @@ + + + diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 08ccc6fea1..768e8ce45c 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -24,6 +24,8 @@ 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-1.4.jar=release/modules/ext/gson-1.4.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 diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 38da548a38..1a48f31d58 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -583,6 +583,24 @@ org.dom4j.util org.dom4j.xpath org.dom4j.xpp + org.freedesktop.gstreamer + org.freedesktop.gstreamer.controller + org.freedesktop.gstreamer.elements + org.freedesktop.gstreamer.elements.good + org.freedesktop.gstreamer.event + org.freedesktop.gstreamer.example + org.freedesktop.gstreamer.glib + org.freedesktop.gstreamer.interfaces + org.freedesktop.gstreamer.io + org.freedesktop.gstreamer.lowlevel + org.freedesktop.gstreamer.lowlevel.annotations + org.freedesktop.gstreamer.media + org.freedesktop.gstreamer.media.event + org.freedesktop.gstreamer.message + org.freedesktop.gstreamer.query + org.freedesktop.gstreamer.swing + org.freedesktop.gstreamer.swt + org.freedesktop.gstreamer.swt.overlay org.gstreamer org.gstreamer.controller org.gstreamer.elements @@ -966,6 +984,10 @@ ext/gstreamer-java-1.5.jar release/modules/ext/gstreamer-java-1.5.jar + + ext/gst1-java-core-0.9.3.jar + release/modules/ext/gst1-java-core-0.9.3.jar + ext/dom4j-1.6.1.jar release/modules/ext/dom4j-1.6.1.jar diff --git a/build-windows-installer.xml b/build-windows-installer.xml index b82e28ca24..ebd60138db 100644 --- a/build-windows-installer.xml +++ b/build-windows-installer.xml @@ -123,7 +123,7 @@ - + diff --git a/build.xml b/build.xml index 5616cb2c66..d5d8532d6e 100644 --- a/build.xml +++ b/build.xml @@ -103,7 +103,7 @@ - +