/* * 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.core package defines the Open Source Physics * media framework for working with video and other media. * * 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.BorderLayout; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.TreeSet; import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextArea; import javax.swing.border.TitledBorder; import javax.swing.filechooser.FileFilter; import org.opensourcephysics.controls.OSPLog; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.controls.XMLControlElement; import org.opensourcephysics.display.OSPRuntime; import org.opensourcephysics.tools.FontSizer; import org.opensourcephysics.tools.ResourceLoader; import org.opensourcephysics.tools.ExtensionsManager; /** * This provides static methods for managing video and text input/output. * * @author Douglas Brown * @version 1.0 */ @SuppressWarnings("unchecked") public class VideoIO { // static constants @SuppressWarnings("javadoc") public static final String[] VIDEO_EXTENSIONS = {"mov", "avi", "mp4"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ @SuppressWarnings("javadoc") public static final String ENGINE_QUICKTIME = "QT"; //$NON-NLS-1$ @SuppressWarnings("javadoc") public static final String ENGINE_XUGGLE = "Xuggle"; //$NON-NLS-1$ @SuppressWarnings("javadoc") public static final String ENGINE_NONE = "none"; //$NON-NLS-1$ @SuppressWarnings("javadoc") public static final String DEFAULT_PREFERRED_EXPORT_EXTENSION = "mp4"; //$NON-NLS-1$ @SuppressWarnings("javadoc") public static final long XUGGLE_54_FILE_LENGTH = 1000000; // smaller than ver 5.4 (38MB) but bigger than 3.4 (.35MB) // static fields protected static JFileChooser chooser; protected static VideoFileFilter videoFileFilter = new VideoFileFilter(); protected static Collection<VideoFileFilter> singleVideoTypeFilters = new TreeSet<VideoFileFilter>(); protected static FileFilter imageFileFilter; protected static ArrayList<VideoType> videoTypes = new ArrayList<VideoType>(); protected static ArrayList<VideoType> videoEngines = new ArrayList<VideoType>(); protected static String defaultXMLExt = "xml"; //$NON-NLS-1$ protected static String videoEngine; protected static VideoEnginePanel videoEnginePanel; protected static boolean canceled; protected static String preferredExportExtension = DEFAULT_PREFERRED_EXPORT_EXTENSION; static { // add video types // add Gif video type, if available try { String name = "org.opensourcephysics.media.gif.GifVideoType"; //$NON-NLS-1$ Class<VideoType> gifClass = (Class<VideoType>)Class.forName(name); addVideoType(gifClass.newInstance()); } catch(Exception ex) { } catch(Error err) { } VideoFileFilter filter = new VideoFileFilter("jpg", //$NON-NLS-1$ new String[] {"jpg", "jpeg"}); //$NON-NLS-1$ //$NON-NLS-2$ addVideoType(new ImageVideoType(filter)); filter = new VideoFileFilter("png", new String[] {"png"}); //$NON-NLS-1$ //$NON-NLS-2$ addVideoType(new ImageVideoType(filter)); imageFileFilter = new FileFilter() { public boolean accept(File f) { if(f==null) { return false; } if(f.isDirectory()) { return true; } String extension = VideoIO.getExtension(f); if((extension!=null)&&(extension.equals("gif")|| //$NON-NLS-1$ extension.equals("jpg"))) {//$NON-NLS-1$ return true; } return false; } public String getDescription() { return MediaRes.getString("VideoIO.ImageFileFilter.Description");//$NON-NLS-1$ } }; // video engine panel videoEnginePanel = new VideoEnginePanel(); } /** * protected constructor to discourage instantiation */ protected VideoIO() { /** empty block */ } /** * Gets the extension of a file. * * @param file the file * @return the extension of the file */ public static String getExtension(File file) { return XML.getExtension(file.getName()); } /** * Gets the file chooser. * * @return the file chooser */ public static JFileChooser getChooser() { if(chooser==null) { File dir = (OSPRuntime.chooserDir==null)? new File(System.getProperty("user.home")): //$NON-NLS-1$ new File(OSPRuntime.chooserDir); chooser = new JFileChooser(dir); chooser.addPropertyChangeListener(videoEnginePanel); } FontSizer.setFonts(chooser, FontSizer.getLevel()); return chooser; } /** * Sets the default xml extension used when saving data. * * @param ext the default extension */ public static void setDefaultXMLExtension(String ext) { defaultXMLExt = ext; } /** * Gets the path relative to the user directory. * * @param absolutePath the absolute path * @return the relative path */ public static String getRelativePath(String absolutePath) { if((absolutePath.indexOf("/")==-1)&&(absolutePath.indexOf("\\")==-1)) { //$NON-NLS-1$ //$NON-NLS-2$ return absolutePath; } if(absolutePath.startsWith("http:")) {//$NON-NLS-1$ return absolutePath; } String path = absolutePath; String relativePath = ""; //$NON-NLS-1$ boolean validPath = false; // relative to user directory String base = System.getProperty("user.dir"); //$NON-NLS-1$ if(base==null) { return path; } for(int j = 0; j<3; j++) { if(j>0) { // move up one level int k = base.lastIndexOf("\\"); //$NON-NLS-1$ if(k==-1) { k = base.lastIndexOf("/"); //$NON-NLS-1$ } if(k!=-1) { base = base.substring(0, k); relativePath += "../"; //$NON-NLS-1$ } else { break; // no more levels! } } if(path.startsWith(base)) { path = path.substring(base.length()+1); // replace backslashes with forward slashes int i = path.indexOf("\\"); //$NON-NLS-1$ while(i!=-1) { path = path.substring(0, i)+"/"+path.substring(i+1); //$NON-NLS-1$ i = path.indexOf("\\"); //$NON-NLS-1$ } relativePath += path; validPath = true; break; } } if(validPath) { return relativePath; } return path; } /** * Determines if a video engine is installed on the current computer. * Note that accessing an installed engine may require switching Java VM. * * @param engine ENGINE_QUICKTIME, ENGINE_XUGGLE, or ENGINE_NONE * @return true if installed */ public static boolean isEngineInstalled(String engine) { if (engine.equals(ENGINE_XUGGLE)) { return ExtensionsManager.getManager().getXuggleJar()!=null; } else if (engine.equals(ENGINE_QUICKTIME)) { if (OSPRuntime.isMac()) return true; if (OSPRuntime.isLinux()) return false; return ExtensionsManager.getManager().getQTJavaZip()!=null; } return false; } /** * Gets the name of the current video engine. * * @return ENGINE_QUICKTIME, ENGINE_XUGGLE, or ENGINE_NONE */ public static String getEngine() { if (videoEngine==null) { videoEngine = getDefaultEngine(); } return videoEngine; } /** * Sets the current video engine by name. * * @param engine ENGINE_QUICKTIME, ENGINE_XUGGLE, or ENGINE_NONE */ public static void setEngine(String engine) { if (engine==null || (!engine.equals(ENGINE_QUICKTIME) && !engine.equals(ENGINE_XUGGLE) && !engine.equals(ENGINE_NONE))) return; videoEngine = engine; } /** * Gets the name of the default video engine. * * @return ENGINE_QUICKTIME, ENGINE_XUGGLE, or ENGINE_NONE */ public static String getDefaultEngine() { String engine = ENGINE_NONE; double xuggleVersion = 0; boolean hasQT = false; for (VideoType next: videoEngines) { if (next.getClass().getSimpleName().contains(ENGINE_XUGGLE)) { xuggleVersion = guessXuggleVersion(); } else if (next.getClass().getSimpleName().contains(ENGINE_QUICKTIME)) { hasQT = true; } } // Xuggle 3.4 is first choice if (xuggleVersion==3.4) engine = ENGINE_XUGGLE; // QuickTime is second choice else if (hasQT) engine = ENGINE_QUICKTIME; // Xuggle 5.4 is last choice--buggy else if (xuggleVersion==5.4) engine = ENGINE_XUGGLE; return engine; } /** * Updates a video engine by copying files or creating symlinks if needed. * * @param engine ENGINE_QUICKTIME, ENGINE_XUGGLE, or ENGINE_NONE * @return true if updated */ public static boolean updateEngine(String engine) { // set up java vm extensions folders String extFolders = XML.forwardSlash(System.getProperty("java.ext.dirs")); //$NON-NLS-1$ ArrayList<File> extDirs = new ArrayList<File>(); String separator = System.getProperty("path.separator"); //$NON-NLS-1$ int n = extFolders.indexOf(separator); while (n>-1) { extDirs.add(new File(extFolders.substring(0, n))); extFolders = extFolders.substring(n+1); n = extFolders.indexOf(separator); } if (!"".equals(extFolders)) //$NON-NLS-1$ extDirs.add(new File(extFolders)); ExtensionsManager manager = ExtensionsManager.getManager(); if (engine.equals(ENGINE_XUGGLE)) { boolean copied = false; for (File extDir: extDirs) { if (!extDir.exists()) continue; copied = manager.copyXuggleJarsTo(extDir) || copied; } return copied; } else if (engine.equals(ENGINE_QUICKTIME)) { if (org.opensourcephysics.display.OSPRuntime.isLinux()) return false; boolean copied = false; for (File extDir: extDirs) { if (!extDir.exists()) continue; copied = manager.copyQTJavaTo(extDir) || copied; } return copied; } return false; } /** * test executing shell commands */ public static void testExec() { // System.getProperties().list(System.out); // // get java vm extensions folder // String extFolder = XML.forwardSlash(System.getProperty("java.ext.dirs")); //$NON-NLS-1$ // // keep only first folder listed // String sep = System.getProperty("path.separator"); // if (extFolder.indexOf(sep) > -1) { // extFolder = extFolder.substring(0, extFolder.indexOf(sep)); // } // extFolder = extFolder+"/"; //$NON-NLS-1$ // // get xuggle folder and jar names // String xuggleHome = System.getenv("XUGGLE_HOME"); //$NON-NLS-1$ // String xuggleFolder = XML.forwardSlash(xuggleHome)+"/share/java/jars/"; //$NON-NLS-1$ // String[] jarNames = {"xuggle-xuggler.jar","slf4j-api.jar", //$NON-NLS-1$ //$NON-NLS-2$ // "logback-core.jar","logback-classic.jar"}; //$NON-NLS-1$ //$NON-NLS-2$ // String shellCmd = "#!/bin/bash\nsudo cp "+xuggleFolder+"xuggle-xuggler.jar "+extFolder+"xuggle-copy.jar"; // shellCmd = "#!/bin/bash\ncp "+xuggleFolder+"xuggle-xuggler.jar ~/junk.jar"; // String home = System.getProperty("user.home"); // String fileName = home+"/copyXuggle.sh"; // try { // File file = new File(fileName); // FileOutputStream stream = new FileOutputStream(file); // java.nio.charset.Charset charset = java.nio.charset.Charset.forName("UTF-8"); //$NON-NLS-1$ // Writer out = new OutputStreamWriter(stream, charset); // Writer output = new BufferedWriter(out); // output.write(shellCmd); // output.flush(); // output.close(); // Runtime.getRuntime().exec("chmod +x "+fileName); // // // open a terminal and write to it // String[] cmd = {"gnome-terminal", "cd ~/Tracker\n"}; // Process process = Runtime.getRuntime().exec(cmd); // new Thread(new StreamPiper(process.getErrorStream(), System.err)).start(); // new Thread(new StreamPiper(process.getInputStream(), System.out)).start(); //// Writer stdin = new OutputStreamWriter(process.getOutputStream()); //// stdin.write(shellCmd); //// stdin.write("gnome-terminal&\n"); //// stdin.write("xterm&\n"); //// stdin.write("cd ~/Tracker\n"); //// stdin.write("dir\n"); //// stdin.write("ls\n"); //// stdin.close(); // // final int exitVal = process.waitFor(); // System.out.println("Exit value: " + exitVal); // } catch(Exception ex) { // ex.printStackTrace(); // } // } /** */ public static class StreamPiper implements Runnable { private final InputStream input; private final OutputStream output; /** * @param in * @param out */ public StreamPiper(InputStream in, OutputStream out) { input = in; output = out; } public void run() { try { final byte[] buffer = new byte[1024]; for (int count = 0; (count = input.read(buffer)) >= 0;) { output.write(buffer, 0, count); } } catch (IOException e) { e.printStackTrace(); } } } /** * Copies a source file to a target file. * * @param inFile the source * @param outFile the target * @return true if successfully copied */ public static boolean copyFile(File inFile, File outFile) { byte[] buffer = new byte[100000]; try { InputStream in = new FileInputStream(inFile); OutputStream out = new FileOutputStream(outFile); while (true) { synchronized (buffer) { int amountRead = in.read(buffer); if (amountRead == -1) { break; } out.write(buffer, 0, amountRead); } } in.close(); out.close(); outFile.setLastModified(inFile.lastModified()); } catch (IOException ex) { return false; } return true; } /** * Returns the currently supported video file extensions * * @return an array of extensions */ public static String[] getVideoExtensions() { return videoFileFilter.getExtensions(); } /** * Gets the preferred file extension for video exports. * * @return the preferred extension */ public static String getPreferredExportExtension() { return preferredExportExtension; } /** * Gets the preferred file extension for video exports. * * @param extension the preferred extension */ public static void setPreferredExportExtension(String extension) { if (extension!=null && extension.length()>1) preferredExportExtension = extension; } /** * Adds a video type to the list of available types * * @param type the video type */ public static void addVideoType(VideoType type) { if(type!=null) { boolean hasType = false; for (VideoType next: videoTypes) { if(next.getDescription().equals(type.getDescription()) && next.getClass()==type.getClass()) { hasType = true; } } if(!hasType) { videoTypes.add(type); VideoFileFilter filter = type.getDefaultFileFilter(); if (filter!=null && filter.extensions!=null) { singleVideoTypeFilters.add(filter); } } } } /** * Adds a video engine to the list of available engines * * @param engine the video engine type */ public static void addVideoEngine(VideoType engine) { if(engine!=null) { OSPLog.finest(engine.getClass().getSimpleName()+" "+engine.getDefaultExtension()); //$NON-NLS-1$ boolean hasType = false; for (VideoType next: videoEngines) { if(next.getClass()==engine.getClass()) { hasType = true; } } if(!hasType) { videoEngines.add(engine); videoEnginePanel.addVideoEngine(engine); } } } /** * Returns the first registered video type corresponding to a class name * and/or extension. Strings are case-insensitive. * * @param className all or part of the simple class name (may be null) * @param extension the extension (may be null) * @return the video type */ public static VideoType getVideoType(String className, String extension) { if (className==null && extension==null) return null; ArrayList<VideoType> candidates = new ArrayList<VideoType>(); synchronized(videoTypes) { // first pass: check class names if (className==null) { candidates.addAll(videoTypes); } else { className = className.toLowerCase(); for (VideoType next: videoTypes) { String name = next.getClass().getSimpleName().toLowerCase(); if (name.indexOf(className)>-1) candidates.add(next); } } if (extension==null) { if (candidates.isEmpty()) return null; return candidates.get(0); } // second pass: compare with default extension extension = extension.toLowerCase(); for (VideoType next: candidates) { String id = next.getDefaultExtension(); if (id!=null && id.indexOf(extension)>-1) return next; } // third pass: compare with all extensions for (VideoType next: candidates) { VideoFileFilter[] filters = next.getFileFilters(); for (VideoFileFilter filter: filters) { if (filter.extensions!=null) { for (String s: filter.extensions) if (s.indexOf(extension)>-1) return next; } } } } return null; } /** * Gets an array of video types that can open files with a given extension. * * @param ext the extension * @return the video types */ public static VideoType[] getVideoTypesForExtension(String ext) { ext = ext.toLowerCase(); ArrayList<VideoType> found = new ArrayList<VideoType>(); // first add types for which ext is the default extension VideoType[] vidTypes = getVideoTypes(); for (VideoType next: vidTypes) { String id = next.getDefaultExtension(); if (id!=null && id.indexOf(ext)>-1) found.add(next); } // then add types for which ext is accepted for (VideoType next: vidTypes) { VideoFileFilter[] filters = next.getFileFilters(); for (VideoFileFilter filter: filters) { if (filter.extensions!=null) { for (String s: filter.extensions) if (s.indexOf(ext)>-1 && !found.contains(next)) found.add(next); } } } return found.toArray(new VideoType[0]); } /** * Gets an array of available video types * * @return the video types */ public static VideoType[] getVideoTypes() { return getVideoTypesForEngine(VideoIO.getEngine()); } /** * Gets an array of video types available to a specified video engine. * Always returns image and gif types in addition to the engine types. * @param engine ENGINE_QUICKTIME, ENGINE_XUGGLE, or ENGINE_NONE * @return the available video types */ public static VideoType[] getVideoTypesForEngine(String engine) { ArrayList<VideoType> available = new ArrayList<VideoType>(); boolean skipQT = VideoIO.getEngine().equals(ENGINE_XUGGLE) || VideoIO.getEngine().equals(ENGINE_NONE); boolean skipXuggle = VideoIO.getEngine().equals(ENGINE_QUICKTIME) || VideoIO.getEngine().equals(ENGINE_NONE); for (VideoType next: videoTypes) { String typeName = next.getClass().getSimpleName(); if (skipQT && typeName.contains(ENGINE_QUICKTIME)) continue; if (skipXuggle && typeName.contains(ENGINE_XUGGLE)) continue; available.add(next); } return available.toArray(new VideoType[0]); } /** * Cancels the current operation when true. * * @param cancel true to cancel */ public static void setCanceled(boolean cancel) { canceled = cancel; } /** * Determines if the current operation is canceled. * * @return true if canceled */ public static boolean isCanceled() { return canceled; } /** * Returns a video from a specified path. May return null. * * @param path the path * @param vidType a requested video type (may be null) * @return the video */ public static Video getVideo(String path, VideoType vidType) { OSPLog.fine("path: "+path+" type: "+vidType); //$NON-NLS-1$ //$NON-NLS-2$ Video video = null; VideoIO.setCanceled(false); // try first with specified VideoType, if any if (vidType!=null) { OSPLog.finest("requested type "+vidType.getClass().getSimpleName() //$NON-NLS-1$ +" "+vidType.getDescription()); //$NON-NLS-1$ video = vidType.getVideo(path); if (video!=null) return video; } if (VideoIO.isCanceled()) return null; // try other allowed video types for the file extension String extension = XML.getExtension(path); VideoType[] allTypes = getVideoTypesForExtension(extension); ArrayList<VideoType> allowedTypes = new ArrayList<VideoType>(); boolean skipQT = VideoIO.getEngine().equals(ENGINE_XUGGLE) || VideoIO.getEngine().equals(ENGINE_NONE); boolean skipXuggle = VideoIO.getEngine().equals(ENGINE_QUICKTIME) || VideoIO.getEngine().equals(ENGINE_NONE); for(int i = 0; i<allTypes.length; i++) { String typeName = allTypes[i].getClass().getSimpleName(); if (skipQT && typeName.contains(ENGINE_QUICKTIME)) continue; if (skipXuggle && typeName.contains(ENGINE_XUGGLE)) continue; allowedTypes.add(allTypes[i]); } for (VideoType next: allowedTypes) { OSPLog.finest("preferred type "+next.getClass().getSimpleName() //$NON-NLS-1$ +" "+next.getDescription()); //$NON-NLS-1$ video = next.getVideo(path); if (VideoIO.isCanceled()) return null; if (video!=null) return video; } return null; } /** * Returns a video from a specified path using a video engine chosen by user. May return null. * * @param path the path * @param engines array of available video types * @param component a JComponent to display with the text (may be null) * @param frame owner of the dialogs (may be null) * @return the video */ public static Video getVideo(String path, ArrayList<VideoType> engines, JComponent component, JFrame frame) { // provide immediate way to open with other engines String engine = VideoIO.getEngine(); engine = VideoIO.ENGINE_NONE.equals(engine)? MediaRes.getString("VideoIO.Engine.None"): //$NON-NLS-1$ VideoIO.ENGINE_XUGGLE.equals(engine)? MediaRes.getString("XuggleVideoType.Description"): //$NON-NLS-1$ MediaRes.getString("QTVideoType.Description"); //$NON-NLS-1$ String message = MediaRes.getString("VideoIO.Dialog.TryDifferentEngine.Message1")+" ("+engine+")."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ message += "\n"+MediaRes.getString("VideoIO.Dialog.TryDifferentEngine.Message2"); //$NON-NLS-1$ //$NON-NLS-2$ message += "\n\n"+MediaRes.getString("VideoIO.Dialog.Label.Path")+": "+path; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ArrayList<String> optionList = new ArrayList<String>(); for (VideoType next: engines) { if (next.getClass().getSimpleName().equals("XuggleVideoType")) { //$NON-NLS-1$ optionList.add(MediaRes.getString("XuggleVideoType.Description")); //$NON-NLS-1$ } else if (next.getClass().getSimpleName().equals("QTVideoType")) { //$NON-NLS-1$ optionList.add(MediaRes.getString("QTVideoType.Description")); //$NON-NLS-1$ } } optionList.add(MediaRes.getString("Dialog.Button.Cancel")); //$NON-NLS-1$ Object[] options = optionList.toArray(new String[optionList.size()]); // assemble message panel with text and checkbox JPanel messagePanel = new JPanel(new BorderLayout()); JTextArea textArea = new JTextArea(); textArea.setText(message); textArea.setOpaque(false); textArea.setBorder(BorderFactory.createEmptyBorder(4, 0, 4, 4)); messagePanel.add(textArea, BorderLayout.NORTH); if (component!=null) { component.setBorder(BorderFactory.createEmptyBorder(12, 0, 12, 0)); messagePanel.add(component, BorderLayout.SOUTH); } int response = JOptionPane.showOptionDialog(frame, messagePanel, MediaRes.getString("VideoClip.Dialog.BadVideo.Title"), //$NON-NLS-1$ JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); if (response>=0 && response<options.length-1) { VideoType desiredType = engines.get(response); Video video = getVideo(path, desiredType); if (video==null && !VideoIO.isCanceled()) { // failed again JOptionPane.showMessageDialog(frame, MediaRes.getString("VideoIO.Dialog.BadVideo.Message")+"\n\n"+path, //$NON-NLS-1$ //$NON-NLS-2$ MediaRes.getString("VideoClip.Dialog.BadVideo.Title"), //$NON-NLS-1$ JOptionPane.WARNING_MESSAGE); } return video; } return null; } /** * Returns a clone of the specified video. * * @param video the video to clone * @return the clone */ public static Video clone(Video video) { if(video==null) { return null; } // ImageVideo is special case since may have pasted images if(video instanceof ImageVideo) { ImageVideo oldVid = (ImageVideo) video; ImageVideo newVid = new ImageVideo(oldVid.getImages()); newVid.rawImage = newVid.images[0]; Collection<Filter> filters = video.getFilterStack().getFilters(); if(filters!=null) { Iterator<Filter> it = filters.iterator(); while(it.hasNext()) { Filter filter = it.next(); newVid.getFilterStack().addFilter(filter); } } return newVid; } XMLControl control = new XMLControlElement(video); return(Video) new XMLControlElement(control).loadObject(null); } /** * Loads the specified video panel from a file selected with a chooser * and sets the data file of the panel. * * @param vidPanel the video panel * @return an array containing the loaded object and file */ public static File open(VideoPanel vidPanel) { return open((File) null, vidPanel); } /** * Displays a file chooser and returns the chosen files. * * @param type may be "open", "open video", "save", "insert image" * @return the files, or null if no files chosen */ public static File[] getChooserFiles(String type) { JFileChooser chooser = getChooser(); chooser.setMultiSelectionEnabled(false); chooser.setAcceptAllFileFilterUsed(true); int result = JFileChooser.CANCEL_OPTION; if(type.toLowerCase().equals("open")) { // open any file //$NON-NLS-1$ chooser.addChoosableFileFilter(videoFileFilter); chooser.setFileFilter(chooser.getAcceptAllFileFilter()); result = chooser.showOpenDialog(null); } else if(type.toLowerCase().equals("open video")) { // open video //$NON-NLS-1$ chooser.addChoosableFileFilter(videoFileFilter); result = chooser.showOpenDialog(null); } else if(type.toLowerCase().equals("save")) { // save any file //$NON-NLS-1$ // note this sets no file filters but does include acceptAll // also sets file name to "untitled" String filename = MediaRes.getString("VideoIO.FileName.Untitled"); //$NON-NLS-1$ chooser.setSelectedFile(new File(filename+"."+defaultXMLExt)); //$NON-NLS-1$ result = chooser.showSaveDialog(null); } else if(type.toLowerCase().equals("insert image")) { //$NON-NLS-1$ chooser.setMultiSelectionEnabled(true); chooser.setAcceptAllFileFilterUsed(false); chooser.addChoosableFileFilter(imageFileFilter); chooser.setSelectedFile(new File("")); //$NON-NLS-1$ result = chooser.showOpenDialog(null); File[] files = chooser.getSelectedFiles(); chooser.removeChoosableFileFilter(imageFileFilter); chooser.setSelectedFile(new File("")); //$NON-NLS-1$ if(result==JFileChooser.APPROVE_OPTION) { return files; } } if(result==JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); chooser.removeChoosableFileFilter(videoFileFilter); chooser.setSelectedFile(new File("")); //$NON-NLS-1$ return new File[] {file}; } return null; } /** * Loads data or a video from a specified file into a VideoPanel. * If file is null, a file chooser is displayed. * * @param file the file to be loaded * @param vidPanel the video panel * @return the file opened */ public static File open(File file, VideoPanel vidPanel) { if(file==null) { File[] files = getChooserFiles("open"); //$NON-NLS-1$ if(files!=null) { file = files[0]; } } if(file==null) { return null; } if(videoFileFilter.accept(file)) { // load video VideoType[] types = getVideoTypes(); Video video = null; for(int i = 0; i<types.length; i++) { video = types[i].getVideo(file.getAbsolutePath()); if(video!=null) { OSPLog.info(file.getName()+" opened as type "+types[i].getDescription()); //$NON-NLS-1$ break; } OSPLog.info(file.getName()+" failed as type "+types[i].getDescription()); //$NON-NLS-1$ } if(video!=null) { vidPanel.setVideo(video); vidPanel.repaint(); } else { JOptionPane.showMessageDialog(vidPanel, MediaRes.getString("VideoIO.Dialog.BadVideo.Message")+ //$NON-NLS-1$ ResourceLoader.getNonURIPath(XML.getAbsolutePath(file))); } } else { // load data XMLControlElement control = new XMLControlElement(); control.read(file.getAbsolutePath()); Class<?> type = control.getObjectClass(); if(VideoPanel.class.isAssignableFrom(type)) { vidPanel.setDataFile(file); control.loadObject(vidPanel); } else if(!control.failedToRead()) { JOptionPane.showMessageDialog(vidPanel, "\""+file.getName()+"\" "+ //$NON-NLS-1$ //$NON-NLS-2$ MediaRes.getString("VideoIO.Dialog.XMLMismatch.Message"), //$NON-NLS-1$ MediaRes.getString("VideoIO.Dialog.XMLMismatch.Title"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$ return null; } else { JOptionPane.showMessageDialog(vidPanel, MediaRes.getString("VideoIO.Dialog.BadFile.Message")+ //$NON-NLS-1$ ResourceLoader.getNonURIPath(XML.getAbsolutePath(file))); } vidPanel.changed = false; } return file; } /** * Writes VideoPanel data to the specified file. If the file is null * it brings up a chooser. * * @param file the file to write to * @param vidPanel the video panel * @return the file written to, or null if not written */ public static File save(File file, VideoPanel vidPanel) { return save(file, vidPanel, MediaRes.getString("VideoIO.Dialog.SaveAs.Title")); //$NON-NLS-1$ } /** * Writes VideoPanel data to the specified file. If the file is null it displays a filechooser. * * @param file the file to write to * @param vidPanel the video panel * @param chooserTitle the title for the filechooser * @return the file written to, or null if not written */ public static File save(File file, VideoPanel vidPanel, String chooserTitle) { if(file==null) { Video video = vidPanel.getVideo(); JFileChooser chooser = getChooser(); chooser.removeChoosableFileFilter(videoFileFilter); chooser.removeChoosableFileFilter(imageFileFilter); chooser.setDialogTitle(chooserTitle); String filename = MediaRes.getString("VideoIO.FileName.Untitled"); //$NON-NLS-1$ if(vidPanel.getFilePath()!=null) { filename = XML.stripExtension(vidPanel.getFilePath()); } else if((video!=null)&&(video.getProperty("name")!=null)) { //$NON-NLS-1$ filename = (String) video.getProperty("name"); //$NON-NLS-1$ int i = filename.lastIndexOf("."); //$NON-NLS-1$ if(i>0) { filename = filename.substring(0, i); } } file = new File(filename+"."+defaultXMLExt); //$NON-NLS-1$ String parent = XML.getDirectoryPath(filename); if(!parent.equals("")) { //$NON-NLS-1$ XML.createFolders(parent); chooser.setCurrentDirectory(new File(parent)); } chooser.setSelectedFile(file); int result = chooser.showSaveDialog(vidPanel); if(result==JFileChooser.APPROVE_OPTION) { file = chooser.getSelectedFile(); if(!defaultXMLExt.equals(getExtension(file))) { filename = XML.stripExtension(file.getPath()); file = new File(filename+"."+defaultXMLExt); //$NON-NLS-1$ } if(file.exists()) { int selected = JOptionPane.showConfirmDialog(vidPanel, " \""+file.getName()+"\" " //$NON-NLS-1$ //$NON-NLS-2$ +MediaRes.getString("VideoIO.Dialog.FileExists.Message"), //$NON-NLS-1$ MediaRes.getString("VideoIO.Dialog.FileExists.Title"), //$NON-NLS-1$ JOptionPane.OK_CANCEL_OPTION); if(selected!=JOptionPane.OK_OPTION) { return null; } } vidPanel.setDataFile(file); } else { return null; } } Video video = vidPanel.getVideo(); if(video!=null) { video.setProperty("base", XML.getDirectoryPath(XML.getAbsolutePath(file))); //$NON-NLS-1$ if(video instanceof ImageVideo) { ((ImageVideo) video).saveInvalidImages(); } } XMLControl xmlControl = new XMLControlElement(vidPanel); xmlControl.write(file.getAbsolutePath()); vidPanel.changed = false; return file; } /** * Writes an image to a file. * @param image the image to write * @param filePath the path to write to, including extension (png, jpg, gif) * @return the file written, or null if failed */ public static File writeImageFile(BufferedImage image, String filePath) { if (image==null) return null; File file = new File(filePath); File parent = file.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } String ext = XML.getExtension(filePath); try { if (ImageIO.write(image, ext, file)) return file; } catch (IOException ex) { OSPLog.finer(ex.toString()); } return null; } /** * Returns the best guess Xuggle version as a double based on file size. * For an exact version number, use DiagnosticsForXuggle (requires Xuggle to be running). * @return 3.4 or 5.4 if xuggle installed, otherwise 0.0 */ public static double guessXuggleVersion() { File xuggleJar = ExtensionsManager.getManager().getXuggleJar(); if (xuggleJar!=null) { return xuggleJar.length()<XUGGLE_54_FILE_LENGTH? 3.4: 5.4; } return 0; } /** * A JPanel for setting a preferred video engine when opening a video. */ protected static class VideoEnginePanel extends JPanel implements PropertyChangeListener { JPanel emptyPanel; File selectedFile; ButtonGroup videoEngineButtonGroup = new ButtonGroup(); HashMap<JRadioButton, VideoType> buttonMap = new HashMap<JRadioButton, VideoType>(); TitledBorder title; boolean isClosing = false; /** * Constructor */ VideoEnginePanel() { super(); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); title = BorderFactory.createTitledBorder(MediaRes.getString("VideoEnginePanel.TitledBorder.Title")); //$NON-NLS-1$ setBorder(title); emptyPanel = new JPanel() { public Dimension getPreferredSize() { return videoEnginePanel.getPreferredSize(); } }; } /** * Adds a video engine type to the available choices. * * @param type the video engine type */ public void addVideoEngine(VideoType type) { JRadioButton button = new JRadioButton(type.getDescription()); button.setActionCommand(type.getClass().getSimpleName()); videoEngineButtonGroup.add(button); buttonMap.put(button, type); add(button); } /** * Gets the selected video engine type. * * @return the video engine type */ public VideoType getSelectedVideoType() { if (chooser.getAccessory()==null || chooser.getAccessory()==emptyPanel) return null; for (JRadioButton button: buttonMap.keySet()) { if (button.isSelected()) { VideoType engineType = buttonMap.get(button); OSPLog.finest("selected video type: "+engineType); //$NON-NLS-1$ String engineName = engineType.getClass().getSimpleName(); String ext = XML.getExtension(selectedFile.getName()); VideoType specificType = getVideoType(engineName, ext); return specificType==null? engineType: specificType; } } return null; } /** * Resets to a ready state. */ public void reset() { isClosing = false; refresh(); } /** * Refreshes the GUI. */ public void refresh() { if (isClosing) return; selectedFile = chooser.getSelectedFile(); // if one or fewer video engines available, don't show this at all! if (buttonMap.size()<2) { chooser.setAccessory(null); chooser.validate(); return; } // count the video engine choices int count = 0; boolean isButtonSelected = false; for (JRadioButton button: buttonMap.keySet()) { if (button.isSelected()) isButtonSelected = true; VideoType type = buttonMap.get(button); for (FileFilter filter: type.getFileFilters()) { if (selectedFile!=null && filter.accept(selectedFile)) { count++; continue; } } } if (count<2) { chooser.setAccessory(emptyPanel); } else { chooser.setAccessory(videoEnginePanel); // select the current video engine by default if (!isButtonSelected) { for (JRadioButton button: buttonMap.keySet()) { // action command is VideoType simple name button.setSelected(button.getActionCommand().contains(VideoIO.getEngine())); } } } chooser.validate(); chooser.repaint(); } /** * Responds to property change event. */ public void propertyChange(PropertyChangeEvent e) { String name = e.getPropertyName(); if (name.toLowerCase().indexOf("closing")>-1) { //$NON-NLS-1$ isClosing = true; } else if (chooser.getAccessory()==null) { return; } else if (name.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) { refresh(); } } } } /* * 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 */