/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.media.core;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.display.Drawable;
import org.opensourcephysics.media.gif.GifVideoRecorder;
import org.opensourcephysics.tools.VideoCaptureTool;
/**
* A video capture utility using media classes.
*
* @author Douglas Brown
* @version 1.0
*/
public class VideoGrabber extends VideoCaptureTool {
/**
* A shared video capture tool.
*/
public static VideoGrabber VIDEO_CAPTURE_TOOL = null;
static Dimension defaultSize = new Dimension(320, 240);
BufferedImage scratch;
VideoRecorder recorder;
VideoType videoType;
VideoPanel recorderPanel;
VideoPanel playerPanel;
String playerFileName;
JFrame recorderFrame;
JFrame playerFrame;
Action clearAction;
Action saveAsAction;
Action recordAction;
Action vidTypeAction;
Action fpsAction;
JButton clearButton;
JButton saveAsButton;
JCheckBox recordCheckBox;
JCheckBox loopCheckBox;
JComboBox vidTypeDropDown;
JLabel fpsLabel;
JComboBox fpsDropDown;
boolean recording = true;
boolean saved = false;
int frameCount = 0;
Dimension imageSize;
int[] pixels = new int[1];
Map<String, VideoType> vidTypes = new HashMap<String, VideoType>();
boolean previewAll = false;
static{ // added by W. Christian
try {
String name = "org.opensourcephysics.media.xuggle.XuggleIO"; //$NON-NLS-1$
Class<?> xuggleClass = Class.forName(name);
Method method=xuggleClass.getMethod("registerWithVideoIO"); //$NON-NLS-1$
method.invoke(null, (Object[])null);
} catch(Exception ex) {
} catch(Error err) {
}
}
/**
* Constructor that uses default video dimensions.
*/
public VideoGrabber() {
this(defaultSize);
}
/**
* Constructor that sets the video dimensions.
*
* @param dim the dimension
*/
public VideoGrabber(Dimension dim) {
super(false);
imageSize = dim;
createGUI();
vidTypeAction.actionPerformed(null);
setRecording(false);
}
/**
* Gets the shared VideoGrabber.
*
* @return the shared VideoGrabber
*/
public static VideoGrabber getTool() {
if(VIDEO_CAPTURE_TOOL==null) {
VIDEO_CAPTURE_TOOL = new VideoGrabber();
}
return VIDEO_CAPTURE_TOOL;
}
/**
* Gets the shared VideoGrabber and sets the video dimensions.
*
* @param dim the dimension
* @return the shared VideoGrabber
*/
public static VideoGrabber getTool(Dimension dim) {
if(VIDEO_CAPTURE_TOOL==null) {
VIDEO_CAPTURE_TOOL = new VideoGrabber(dim);
} else {
VIDEO_CAPTURE_TOOL.imageSize = dim;
VIDEO_CAPTURE_TOOL.recorderPanel.setPreferredSize(dim);
VIDEO_CAPTURE_TOOL.recorderFrame.pack();
}
return VIDEO_CAPTURE_TOOL;
}
/**
* Clear the video from the tool in preparation for a new video.
*/
public void clear() {
// setRecording(false);
clearAction.actionPerformed(null);
}
/**
* Adds a frame to the video if it is recording.
*
* @param image the frame to be added
* @return true if frame was added
*/
public boolean addFrame(BufferedImage image) {
if(isRecording()) {
try {
int w = image.getWidth();
int h = image.getHeight();
// added by WC
int remainderW=(w%16);
int remainderH=(h%16);
if(remainderW!=0 || remainderH!=0){ // crop image
image=image.getSubimage(remainderW/2, remainderH/2, w-remainderW, h-remainderH);
w = image.getWidth();
h = image.getHeight();
}
// end of added code
if(pixels.length!=w*h) {
pixels = new int[w*h];
}
boolean newScratch = false;
if(previewAll) {
scratch = null;
} else if((scratch==null)||(scratch.getWidth()!=w)||(scratch.getHeight()!=h)) {
scratch = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
newScratch = true;
}
BufferedImage copy = previewAll ?
new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB) : scratch;
image.getRaster().getDataElements(0, 0, w, h, pixels);
copy.getRaster().setDataElements(0, 0, w, h, pixels);
Video video = recorderPanel.getVideo();
if(video==null) { // first frame added
recorderPanel.setVideo(new ImageVideo(copy));
recorderPanel.getPlayer().setReadoutTypes("frame", null); //$NON-NLS-1$
if(recorder instanceof GifVideoRecorder) {
int i = loopCheckBox.isSelected() ? 0 : 1;
((GifVideoRecorder) recorder).getGifEncoder().setRepeat(i);
}
} else if(previewAll) {
ImageVideo imageVid = (ImageVideo) video;
imageVid.insert(new Image[] {copy}, imageVid.getFrameCount(), null);
} else if(newScratch) {
recorderPanel.setVideo(new ImageVideo(copy));
recorderPanel.getPlayer().setReadoutTypes("frame", null); //$NON-NLS-1$
}
recorder.addFrame(copy);
frameCount++;
String item = (String) fpsDropDown.getSelectedItem();
double dt = 1000.0/Double.parseDouble(item);
recorderPanel.getPlayer().getClipControl().setFrameDuration(dt);
recorderPanel.getPlayer().getVideoClip().setStepCount(frameCount);
recorderPanel.getPlayer().setStepNumber(frameCount-1);
if(video==null) { // first frame added
imageSize = new Dimension(image.getWidth(), image.getHeight());
// give recorderPanel extra room so whole video is visible
final Dimension dim = new Dimension(image.getWidth()+4, image.getHeight()+4);
Runnable runner = new Runnable() {
public void run() {
recorderPanel.setPreferredSize(dim);
recorderFrame.pack();
}
};
SwingUtilities.invokeLater(runner);
refreshGUI();
}
return true;
} catch(IOException ex) {
return false;
}
}
return false;
}
/**
* Sets the visibility.
*
* @param visible true to set this visible
*/
public void setVisible(boolean visible) {
recorderFrame.setVisible(visible);
}
/**
* Gets the visibility.
*
* @return true if visible
*/
public boolean isVisible() {
return recorderFrame.isVisible();
}
/**
* Sets the recording flag.
*
* @param record true to record rendered images
*/
public void setRecording(boolean record) {
recording = record;
refreshGUI();
}
/**
* Gets the recording flag.
*
* @return true if recording rendered images
*/
public boolean isRecording() {
return recording&&(recorder!=null);
}
/**
* Sets the video type.
*
* @param type the video type
*/
public void setVideoType(VideoType type) {
if((type==null)||(type==videoType)) {
return;
}
videoType = type;
recorder = type.getRecorder();
clearAction.actionPerformed(null);
}
/**
* Sets the frame rate.
*
* @param fps the frame rate in frames per second
*/
public void setFrameRate(double fps) {
fps = Math.max(fps, 1);
fps = Math.min(fps, 60);
fps = Math.round(100*fps)/100; // round to nearest .01 fps
int n = fpsDropDown.getItemCount();
for(int i = 0; i<n; i++) {
String item = (String)fpsDropDown.getItemAt(i);
double dropdownValue = Double.parseDouble(item);
if(fps==dropdownValue) {
fpsDropDown.setSelectedIndex(i);
return;
}
if(fps>dropdownValue) {
String s = String.valueOf(fps);
fpsDropDown.insertItemAt(s, i);
fpsDropDown.setSelectedItem(s);
return;
}
}
}
/**
* Saves the video to a file and returns the file name.
*
* @return the name of the file, or null if not saved
*/
public String saveVideoAs() {
if(recorder!=null) {
try {
return recorder.saveVideoAs();
} catch(IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "File Not Saved", JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
}
}
return null;
}
/**
* Gets the video recorder.
*
* @return the VideoRecorder
*/
public VideoRecorder getRecorder() {
return recorder;
}
/**
* Creates the GUI.
*/
protected void createGUI() {
createActions();
// create buttons and dropdowns
clearButton = new JButton(clearAction);
saveAsButton = new JButton(saveAsAction);
recordCheckBox = new JCheckBox(recordAction);
recordCheckBox.setOpaque(false);
loopCheckBox = new JCheckBox();
loopCheckBox.setOpaque(false);
// fps label and dropdown
fpsLabel = new JLabel();
String[] rates = {"30", "29.97", "25", "20", "15", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
"12", "10", "8", "6", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
"5", "4", "3", "2", "1"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
fpsDropDown = new JComboBox(rates) {
public Dimension getMaximumSize() {
return getMinimumSize();
}
};
fpsDropDown.addActionListener(fpsAction);
// video type dropdown
vidTypeDropDown = new JComboBox() {
public Dimension getMaximumSize() {
return getMinimumSize();
}
};
// make all VideoIO VideoTypes available to the user
// added by D Brown 29 Oct 2011
for (VideoType next: VideoIO.getVideoTypes()) {
if (!next.canRecord()) continue;
String desc = next.getDescription();
vidTypes.put(desc, next);
vidTypeDropDown.addItem(desc);
}
vidTypeDropDown.addActionListener(vidTypeAction);
// create and assemble frame
recorderFrame = new JFrame();
recorderFrame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
recorderFrame.setName("VideoCaptureTool"); //$NON-NLS-1$
recorderFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if(recorder instanceof ScratchVideoRecorder) {
ScratchVideoRecorder svr = (ScratchVideoRecorder) recorder;
if(svr.scratchFile!=null) {
try {
svr.saveScratch();
svr.scratchFile.delete();
} catch(IOException ex) {
/** empty block */
}
}
}
}
});
JPanel contentPane = new JPanel(new BorderLayout());
recorderFrame.setContentPane(contentPane);
recorderPanel = new FixedSizeVideoPanel();
recorderPanel.setPreferredSize(imageSize);
// bottom panel contains player and buttonBar
JPanel bottomPanel = new JPanel(new BorderLayout());
contentPane.add(bottomPanel, BorderLayout.SOUTH);
JToolBar playerBar = new JToolBar();
playerBar.setFloatable(false);
recorderPanel.getPlayer().setBorder(null);
recorderPanel.setPlayerVisible(false);
recorderPanel.getPlayer().setLoopingButtonVisible(false);
contentPane.add(recorderPanel, BorderLayout.CENTER);
JToolBar buttonBar = new JToolBar();
buttonBar.setFloatable(false);
if(previewAll) {
playerBar.add(recorderPanel.getPlayer());
bottomPanel.add(playerBar, BorderLayout.CENTER);
} else {
buttonBar.add(recorderPanel.getPlayer().readout);
}
bottomPanel.add(buttonBar, BorderLayout.SOUTH);
buttonBar.add(recordCheckBox);
buttonBar.add(Box.createHorizontalGlue());
buttonBar.add(clearButton);
buttonBar.add(saveAsButton);
// topBar contains other components
JToolBar topBar = new JToolBar();
topBar.setFloatable(false);
contentPane.add(topBar, BorderLayout.NORTH);
topBar.add(vidTypeDropDown);
topBar.addSeparator();
topBar.add(fpsLabel);
topBar.add(fpsDropDown);
topBar.addSeparator();
topBar.add(loopCheckBox);
topBar.add(Box.createHorizontalGlue());
recorderFrame.pack();
// position recorderFrame in top center of screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-recorderFrame.getBounds().width)/2;
recorderFrame.setLocation(x, 0);
}
/**
* Creates the actions.
*/
protected void createActions() {
clearAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
recorderPanel.setVideo(null);
recorderPanel.getPlayer().getVideoClip().setStepCount(1);
if(recorder!=null) {
String item = (String) fpsDropDown.getSelectedItem();
double dt = 1000.0/Double.parseDouble(item);
recorder.setFrameDuration(dt);
try {
recorder.createVideo();
frameCount = 0;
saved = false;
recorderPanel.setVideo(null);
recorderPanel.getPlayer().setReadoutTypes("frame", null); //$NON-NLS-1$
recorderPanel.getPlayer().getVideoClip().setStepCount(1);
refreshGUI();
} catch(IOException ex) {
ex.printStackTrace();
}
}
System.gc(); // recover resources used by previous ImageVideo
}
};
saveAsAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if(recorder!=null) {
try {
String name = recorder.saveVideoAs();
if(name!=null) {
Video video = videoType.getVideo(name);
if(playerPanel==null) {
playerPanel = new FixedSizeVideoPanel();
playerFrame = new VideoFrame(playerPanel);
int w = imageSize.width+4;
int h = imageSize.height+recorderPanel.getPlayer().height+4;
playerPanel.setPreferredSize(new Dimension(w, h));
playerFrame.pack();
}
playerPanel.setVideo(video);
if(loopCheckBox.isVisible()&&loopCheckBox.isSelected()) {
playerPanel.getPlayer().setLooping(true);
playerPanel.getPlayer().play();
}
playerFrame.setVisible(true);
saved = true;
playerFileName = name;
clearAction.actionPerformed(null);
}
} catch(IOException ex) {
ex.printStackTrace();
}
}
}
};
recordAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
recording = !recording;
refreshGUI();
}
};
vidTypeAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
Object desc = vidTypeDropDown.getSelectedItem();
VideoType vidType = vidTypes.get(desc);
if(vidType!=null) {
setVideoType(vidType);
}
}
};
fpsAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
clearAction.actionPerformed(null);
}
};
}
/**
* Refreshes the GUI.
*/
protected void refreshGUI() {
recordCheckBox.setSelected(isRecording());
clearButton.setEnabled(frameCount!=0);
saveAsButton.setEnabled(frameCount!=0);
vidTypeDropDown.setEnabled(frameCount==0);
vidTypeDropDown.setSelectedItem(videoType.getDescription());
fpsDropDown.setEnabled(frameCount==0);
fpsDropDown.setVisible(!(videoType instanceof ImageVideoType));
fpsLabel.setVisible(!(videoType instanceof ImageVideoType));
loopCheckBox.setEnabled(frameCount==0);
recorderPanel.getPlayer().readout.setEnabled(frameCount!=0);
recordCheckBox.setText(MediaRes.getString("VideoGrabber.Action.Capture")); //$NON-NLS-1$
loopCheckBox.setText(MediaRes.getString("VideoGrabber.Action.Loop")); //$NON-NLS-1$
loopCheckBox.setVisible(videoType.getClass().getSimpleName().equals("GifVideoType")); //$NON-NLS-1$
fpsLabel.setText(MediaRes.getString("VideoGrabber.Label.PlayRate")+" "); //$NON-NLS-1$ //$NON-NLS-2$
clearButton.setText(MediaRes.getString("VideoGrabber.Action.Clear")); //$NON-NLS-1$
saveAsButton.setText(MediaRes.getString("VideoGrabber.Action.SaveAs")); //$NON-NLS-1$
if(recordCheckBox.isSelected()) {
recorderFrame.setTitle(MediaRes.getString("VideoGrabber.Title.Capturing") //$NON-NLS-1$
+videoType.getDescription());
} else {
recorderFrame.setTitle(videoType.getDescription());
}
if((playerFrame!=null)&&playerFrame.isVisible()) {
playerFrame.setTitle(MediaRes.getString("VideoGrabber.Title.Saved") //$NON-NLS-1$
+XML.getName(playerFileName));
}
}
private class FixedSizeVideoPanel extends VideoPanel {
FixedSizeVideoPanel() {
setBackground(Color.black);
setDrawingInImageSpace(true);
setShowCoordinates(false);
}
protected void scale(ArrayList<Drawable> drawables) {
// set image border so video size remains fixed
double w = imageWidth;
double wBorder = (getWidth()-w-1)*0.5/w;
double h = imageHeight;
double hBorder = (getHeight()-h-1)*0.5/h;
double border = Math.min(wBorder, hBorder);
super.setImageBorder(border);
super.scale(drawables);
}
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/