/*
* 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/>
*/
/*
* The org.opensourcephysics.media.gif package provides GIF services
* including implementations of the Video and VideoRecorder interfaces.
*
* Copyright (c) 2014 Douglas Brown and Wolfgang Christian.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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
*
* For additional information and documentation on Open Source Physics,
* please see <http://www.opensourcephysics.org/>.
*/
package org.opensourcephysics.media.core;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLControlElement;
import org.opensourcephysics.tools.ResourceLoader;
/**
* This is an image video recorder that uses scratch files.
*
* @author Douglas Brown
* @version 1.0
*/
public class ImageVideoRecorder extends ScratchVideoRecorder {
// instance fields
protected int frameCount;
private String tempFileBasePath;
private String tempFileType = "png"; //$NON-NLS-1$
private String[] savedFilePaths;
/**
* Constructs a default ImageVideoRecorder object.
*/
public ImageVideoRecorder() {
super(new ImageVideoType());
}
/**
* Constructs a ImageVideoRecorder object for a specific image type.
* @param type the image type
*/
public ImageVideoRecorder(ImageVideoType type) {
super(type);
String ext = type.getDefaultExtension();
if (ext!=null)
tempFileType = ext;
}
/**
* Gets the video.
*
* @return the video
* @throws IOException
*/
public Video getVideo() throws IOException {
if (saveFile!=null) {
if (!isSaved) saveScratch();
if (savedFilePaths!=null && savedFilePaths.length>0) {
ImageVideo video = new ImageVideo(savedFilePaths[0], savedFilePaths.length>1);
video.setFrameDuration(frameDuration);
return video;
}
}
return null;
}
/**
* Saves all video images to a numbered sequence of files.
*
* @param fileName the file name basis for images
* @return the full path of the first image in the sequence
* @throws IOException
*/
public String saveVideo(String fileName) throws IOException {
if(fileName==null) {
return saveVideoAs();
}
setFileName(fileName);
if(saveFile==null) {
throw new IOException("Read-only file"); //$NON-NLS-1$
}
saveScratch();
return (savedFilePaths==null || savedFilePaths.length==0)?
null : savedFilePaths[0];
}
/**
* Sets the expected frame count.
*
* @param n the expected frame count
*/
public void setExpectedFrameCount(int n) {
frameCount = n;
}
/**
* Discards the current video and resets the recorder to a ready state.
*/
@Override
public void reset() {
frameCount = 0;
deleteTempFiles();
super.reset();
}
/**
* Called by the garbage collector when this recorder is no longer in use.
*/
@Override
protected void finalize() {
reset();
}
//________________________________ protected methods _________________________________
/**
* Required by ScratchVideoRecorder, but unused.
*/
protected void saveScratch() throws IOException {
if (!hasContent) return;
// if chooser was used, check the fileFilter for video type
if (chosenExtension!=null && !(chooser.getFileFilter() instanceof VideoFileFilter))
return;
// copy temp files or open and re-encode if needed
synchronized (tempFiles) {
String fileName = saveFile.getAbsolutePath();
savedFilePaths = getFileNames(fileName, tempFiles.size());
for (int i = 0; i < tempFiles.size(); i++) {
String path = savedFilePaths[i];
File tempFile = tempFiles.get(i);
if (!tempFile.exists()) {
savedFilePaths = null;
throw new IOException("temp image file not found"); //$NON-NLS-1$
}
if (ext==tempFileType) {
// copy images
File targetFile = new File(path);
VideoIO.copyFile(tempFile, targetFile);
}
else {
// open and encode images in desired format
BufferedImage image = ResourceLoader.getBufferedImage(tempFile.getAbsolutePath());
if (image==null) {
throw new IOException("unable to load temp image file"); //$NON-NLS-1$
}
javax.imageio.ImageIO.write(image, ext,
new BufferedOutputStream(new FileOutputStream(path)));
}
}
}
deleteTempFiles();
isSaved = true;
hasContent = false;
canRecord = false;
if (savedFilePaths!=null && savedFilePaths.length>0) {
// save xml description of the video (for frame duration)
Video video = getVideo();
XMLControl control = new XMLControlElement(video);
String fileName = savedFilePaths[0];
fileName = XML.stripExtension(fileName)+".xml"; //$NON-NLS-1$
control.write(fileName);
}
}
/**
* Starts the video recording process.
*
* @return true if video recording successfully started
*/
protected boolean startRecording() {
if(dim==null) {
if(frameImage!=null) {
dim = new Dimension(frameImage.getWidth(null), frameImage.getHeight(null));
} else {
return false;
}
}
try {
tempFileBasePath = XML.stripExtension(scratchFile.getAbsolutePath());
} catch (Exception e) {
return false;
}
return true;
}
// /**
// * Appends a frame to the current video. Note: this creates a new
// * BufferedImage each time a frame is appended and can use lots of
// * memory in a hurry.
// *
// * @param image the image to append
// * @return true if image successfully appended
// */
// protected boolean append(Image image) {
// int w = image.getWidth(null);
// int h = image.getHeight(null);
// BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
// Graphics2D g = bi.createGraphics();
// g.drawImage(image, 0, 0, null);
// images.add(bi);
// return true;
// }
/**
* Appends a frame to the current video by saving the image in a tempFile.
*
* @param image the image to append
* @return true if image successfully appended
*/
@Override
protected boolean append(Image image) {
int w = image.getWidth(null);
int h = image.getHeight(null);
if (dim==null) {
dim = new Dimension(w, h);
}
// can't append images that are different size than first
if (dim.width!=w || dim.height!=h)
return false;
// convert to BufferedImage if needed
if (!(image instanceof BufferedImage)) {
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
img.getGraphics().drawImage(image, 0, 0, null);
image = img;
}
BufferedImage source = (BufferedImage)image;
String fileName = tempFileBasePath+"_"+tempFiles.size()+".tmp"; //$NON-NLS-1$ //$NON-NLS-2$
try {
ImageIO.write(source, tempFileType, new BufferedOutputStream(
new FileOutputStream(fileName)));
} catch (Exception e) {
return false;
}
File imageFile = new File(fileName);
if (imageFile.exists()) {
synchronized (tempFiles) {
tempFiles.add(imageFile);
}
imageFile.deleteOnExit();
}
return true;
}
/**
* Return the file that will be saved if the specified file is selected.
* This is needed by ImageVideoRecorder since it strips and/or appends digits
* to the selected file name.
*
* @param file the file selected with the chooser
* @return the file (or first file) to be saved
*/
protected File getFileToBeSaved(File file) {
// determine number of digits to append to file names
int n = frameCount>0? frameCount: tempFiles.size();
// if single or no image, return file itself
if(n<=1) {
return file;
}
String fileName = file.getAbsolutePath();
// get appended number
int i = getAppendedNumber(fileName);
// get base
String base = getBase(fileName);
if(i>0) {
fileName = base+((n+i<10) ? String.valueOf(i) : ((n+i<100)&&(i<10)) ? "0"+i : ( //$NON-NLS-1$
n+i<100) ? String.valueOf(i) : ((n+i<1000)&&(i<10)) ? "00"+i : (( //$NON-NLS-1$
n+i<1000)&&(i<100)) ? "0"+i : ( //$NON-NLS-1$
n+i<1000) ? String.valueOf(i) : (i<10) ? "000"+i : ( //$NON-NLS-1$
i<100) ? "00"+i : ( //$NON-NLS-1$
i<1000) ? "0"+i : //$NON-NLS-1$
String.valueOf(i));
} else {
fileName = base+((n<10) ? "0" : ( //$NON-NLS-1$
n<100) ? "00" : ( //$NON-NLS-1$
n<1000) ? "000" : //$NON-NLS-1$
"0000"); //$NON-NLS-1$
}
if (ext!=null)
fileName += "."+ext; //$NON-NLS-1$
return new File(fileName);
}
/**
* Saves images to a numbered sequence of jpg files.
*
* @param fileName the file name basis for images
* @param images the images to save
* @return the paths of the saved images
* @throws IOException
*/
protected static String[] saveImages(String fileName, BufferedImage[] images) throws IOException {
String[] fileNames = getFileNames(fileName, images.length);
for(int i = 0; i<images.length; i++) {
String next = fileNames[i];
javax.imageio.ImageIO.write(images[i], ext,
new BufferedOutputStream(new FileOutputStream(next)));
}
return fileNames;
}
protected static String[] getFileNames(String fileName, int length) {
if(length==1) return new String[] {fileName};
// determine number of digits to append to file names
int k = getAppendedNumber(fileName);
ArrayList<String> paths = new ArrayList<String>();
int digits = (length+k<10) ? 1 : (length+k<100) ? 2 : (length+k<1000) ? 3 : 4;
// get base
String base = getBase(fileName);
for(int i = 0; i<length; i++) {
// append numbers and save images
String num = String.valueOf(i+k);
if((digits==2)&&(i+k<10)) {
num = "0"+num; //$NON-NLS-1$
} else if((digits==3)&&(i+k<10)) {
num = "00"+num; //$NON-NLS-1$
} else if((digits==3)&&(i+k<100)) {
num = "0"+num; //$NON-NLS-1$
} else if((digits==4)&&(i+k<10)) {
num = "000"+num; //$NON-NLS-1$
} else if((digits==4)&&(i+k<100)) {
num = "00"+num; //$NON-NLS-1$
} else if((digits==4)&&(i+k<1000)) {
num = "0"+num; //$NON-NLS-1$
}
fileName = base+num+"."+ext; //$NON-NLS-1$
paths.add(fileName);
}
return paths.toArray(new String[0]);
}
protected static String getBase(String path) {
String base = XML.stripExtension(path);
// strip off digits at end, if any
int len = base.length();
int digits = 1;
for(; digits<len; digits++) {
try {
Integer.parseInt(base.substring(len-digits));
} catch(NumberFormatException ex) {
break;
}
}
digits--; // failed at digits, so go back one
if(digits==0) { // no number found
return base;
}
return base.substring(0, len-digits);
}
protected static int getAppendedNumber(String path) {
String base = XML.stripExtension(path);
// look for appended number at end, if any
int len = base.length();
int digits = 1;
int n = 0;
for(; digits<len; digits++) {
try {
n = Integer.parseInt(base.substring(len-digits));
} catch(NumberFormatException ex) {
break;
}
}
return n;
}
}
/*
* 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
*/