/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2004-07 Ben Fry and Casey Reas The previous version of this code was developed by Hernando Barragan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.video; import processing.core.*; import java.io.*; import java.lang.reflect.*; import quicktime.*; import quicktime.io.QTFile; import quicktime.qd.*; import quicktime.std.*; import quicktime.std.movies.media.DataRef; import quicktime.util.QTHandle; import quicktime.util.RawEncodedImage; @SuppressWarnings("deprecation") public class Movie extends PImage implements PConstants, Runnable { // no longer needing a reference to the parent because PImage has one //PApplet parent; Method movieEventMethod; String filename; Thread runner; PImage borderImage; boolean removeBorders = true; boolean play; boolean repeat; boolean available; int fps; /** * The QuickTime for Java "Movie" object, made public * in case anyone wants to play with it. */ public quicktime.std.movies.Movie movie; QDRect movieRect; QDGraphics movieGraphics; boolean firstFrame = true; RawEncodedImage raw; /* static { try { //System.out.println("jlp = " + System.getProperty("java.library.path")); QTSession.open(); } catch (QTException e) { e.printStackTrace(); } // shutting off for 0116, hoping for better exception handling QTRuntimeException.registerHandler(new QTRuntimeHandler() { public void exceptionOccurred(QTRuntimeException e, Object obj, String s, boolean flag) { System.err.println("Problem inside Movie"); e.printStackTrace(); } }); } */ public Movie(PApplet parent, String filename) { this(parent, filename, 30); } public Movie(final PApplet parent, final String filename, final int ifps) { // this creates a fake image so that the first time this // attempts to draw, something happens that's not an exception super(1, 1, RGB); // http://dev.processing.org/bugs/show_bug.cgi?id=882 //SwingUtilities.invokeLater(new Runnable() { //public void run() { init(parent, filename, ifps); //} //}); } public void init(PApplet parent, String filename, int fps) { this.parent = parent; this.fps = fps; try { QTSession.open(); } catch (QTException e) { e.printStackTrace(); return; } // first check to see if this can be read locally from a file. // otherwise, will have to load the file into memory, which is // gonna make people unhappy who are trying to play back 50 MB // quicktime movies with a locally installed piece exported // as an application. try { try { // first try a local file using the dataPath. usually this will // work ok, but sometimes the dataPath is inside a jar file, // which is less fun, so this will crap out. File file = new File(parent.dataPath(filename)); if (file.exists()) { movie = fromDataRef(new DataRef(new QTFile(file))); //init(parent, movie, ifps); //return; } } catch (Exception e) { } // ignored // read from a folder local to the current working dir // called "data". presumably this might be the data folder, // though that should be caught above, if such a folder exists. /* if (movie == null) { try { File file = new File("data", filename); if (file.exists()) { movie = fromDataRef(new DataRef(new QTFile(file))); init(parent, movie, ifps); return; } } catch (QTException e2) { } } */ // read from a file just hanging out in the local folder. // this might happen when the video library is used with some // other application, or the person enters a full path name if (movie == null) { try { File file = new File(filename); if (file.exists()) { movie = fromDataRef(new DataRef(new QTFile(file))); //init(parent, movie, ifps); //return; } } catch (QTException e1) { } } } catch (SecurityException se) { // online, whups. catch the security exception out here rather than // doing it three times (or whatever) for each of the cases above. } // if the movie can't be read from a local file, it has to be read // into a byte array and passed to qtjava. it's annoying that apple // doesn't have something in the api to read a movie from a friggin // InputStream, but oh well. it's their api. if (movie == null) { byte data[] = parent.loadBytes(filename); //int dot = filename.lastIndexOf("."); // grab the extension from the file, use mov if there is none //String extension = (dot == -1) ? "mov" : // filename.substring(dot + 1).toLowerCase(); try { movie = fromDataRef(new DataRef(new QTHandle(data))); } catch (QTException e) { e.printStackTrace(); } } /* URL url = null; this.filename = filename; // for error messages if (filename.startsWith("http://")) { try { url = new URL(filename); DataRef urlRef = new DataRef(url.toExternalForm()); movie = fromDataRef(urlRef); init(parent, movie, ifps); return; } catch (QTException qte) { qte.printStackTrace(); return; } catch (MalformedURLException e) { e.printStackTrace(); return; } } // updated for new loading style of 0096 ClassLoader cl = parent.getClass().getClassLoader(); url = cl.getResource("data/" + filename); if (url != null) { init(parent, url, ifps); return; } try { try { File file = new File(parent.dataPath(filename)); if (file.exists()) { movie = fromDataRef(new DataRef(new QTFile(file))); init(parent, movie, ifps); return; } } catch (Exception e) { } // ignored try { File file = new File("data", filename); if (file.exists()) { movie = fromDataRef(new DataRef(new QTFile(file))); init(parent, movie, ifps); return; } } catch (QTException e2) { } try { File file = new File(filename); if (file.exists()) { movie = fromDataRef(new DataRef(new QTFile(file))); init(parent, movie, ifps); return; } } catch (QTException e1) { } } catch (SecurityException se) { } // online, whups */ if (movie == null) { parent.die("Could not find movie file " + filename, null); } // we've got a valid movie! let's rock. try { // this is probably causing the 2 seconds of audio // disabled pre-preroll on 0126 because of security problems //movie.prePreroll(0, 1.0f); movie.preroll(0, 1.0f); // this has a possibility of running forever.. // should probably happen on the thread as well. while (movie.maxLoadedTimeInMovie() == 0) { movie.task(100); // 0106: tried adding sleep time so this doesn't spin out of control // works fine but doesn't really help anything //try { //Thread.sleep(5); //} catch (InterruptedException e) { } } movie.setRate(1); //fps = ifps; // register methods parent.registerDispose(this); try { movieEventMethod = parent.getClass().getMethod("movieEvent", new Class[] { Movie.class }); } catch (Exception e) { // no such method, or an error.. which is fine, just ignore } // and now, make the magic happen runner = new Thread(this); runner.start(); } catch (QTException qte) { qte.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /* public Movie(PApplet parent, URL url) { init(parent, url, 30); } public Movie(PApplet parent, URL url, int ifps) { init(parent, url, ifps); } public void init(PApplet parent, URL url, int ifps) { String externalized = url.toExternalForm(); System.out.println("externalized is " + externalized); // qtjava likes file: urls to read file:/// not file:/ // so this changes them when appropriate if (externalized.startsWith("file:/") && !externalized.startsWith("file:///")) { externalized = "file:///" + url.getPath(); } // the url version is the only available that can take // an InputStream (indirectly) since it uses url syntax //DataRef urlRef = new DataRef(requestFile); try { System.out.println(url); System.out.println(externalized); DataRef urlRef = new DataRef(externalized); System.out.println(urlRef); movie = fromDataRef(urlRef); init(parent, movie, ifps); } catch (QTException e) { e.printStackTrace(); } } */ /** * Why does this function have to be so bizarre? i love the huge * constants! i think they're neato. i feel like i'm coding for * think pascal on my mac plus! those were happier times. */ private quicktime.std.movies.Movie fromDataRef(DataRef ref) throws QTException { return quicktime.std.movies.Movie.fromDataRef(ref, StdQTConstants4.newMovieAsyncOK | StdQTConstants.newMovieActive); } /* public void init(PApplet parent, quicktime.std.movies.Movie movie, int ifps) { this.parent = parent; try { // this is probably causing the 2 seconds of audio movie.prePreroll(0, 1.0f); movie.preroll(0, 1.0f); // this has a possibility of running forever.. // should probably happen on the thread as well. while (movie.maxLoadedTimeInMovie() == 0) { movie.task(100); // 0106: tried adding sleep time so this doesn't spin out of control // works fine but doesn't really help anything //try { //Thread.sleep(5); //} catch (InterruptedException e) { } } movie.setRate(1); fps = ifps; runner = new Thread(this); runner.start(); // register methods parent.registerDispose(this); try { movieEventMethod = parent.getClass().getMethod("movieEvent", new Class[] { Movie.class }); } catch (Exception e) { // no such method, or an error.. which is fine, just ignore } } catch (QTException qte) { qte.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } */ public boolean available() { return available; } public void read() { try { if (firstFrame) { movieRect = movie.getBox(); //movieGraphics = new QDGraphics(movieRect); if (quicktime.util.EndianOrder.isNativeLittleEndian()) { movieGraphics = new QDGraphics(QDConstants.k32BGRAPixelFormat, movieRect); } else { movieGraphics = new QDGraphics(QDGraphics.kDefaultPixelFormat, movieRect); } } Pict pict = movie.getPict(movie.getTime()); // returns an int pict.draw(movieGraphics, movieRect); PixMap pixmap = movieGraphics.getPixMap(); raw = pixmap.getPixelData(); // It needs to get at least a small part // of the video to get the parameters if (firstFrame) { //int intsPerRow = pixmap.getRowBytes() / 4; int movieWidth = movieRect.getWidth(); int movieHeight = movieRect.getHeight(); int j = raw.getRowBytes() - movieWidth*4; // this doesn't round up.. does it need to? int k = j / 4; int dataWidth = movieWidth + k; if (dataWidth != movieWidth) { if (removeBorders) { borderImage = new PImage(dataWidth, movieHeight, RGB); } else { movieWidth = dataWidth; } } //int vpixels[] = new int[movieWidth * movieHeight]; //image = new PImage(vpixels, movieWidth, movieHeight, RGB); super.init(movieWidth, movieHeight, RGB); //parent.video = image; firstFrame = false; } // this happens later (found by hernando) //raw.copyToArray(0, image.pixels, 0, image.width * image.height); loadPixels(); // this is identical to a chunk of code inside PCamera // this might be a candidate to move up to PVideo or something if (borderImage != null) { // need to remove borders raw.copyToArray(0, borderImage.pixels, 0, borderImage.width * borderImage.height); int borderIndex = 0; int targetIndex = 0; for (int i = 0; i < height; i++) { System.arraycopy(borderImage.pixels, borderIndex, pixels, targetIndex, width); borderIndex += borderImage.width; targetIndex += width; } } else { // just copy directly raw.copyToArray(0, pixels, 0, width * height); } // ready to rock //System.out.println("updating pixels"); //updatePixels(); // mark as modified updatePixels(); } catch (QTException qte) { qte.printStackTrace(); //QTSession.close(); // let dispose() handle it } } /** * Begin playing the movie, with no repeat. */ public void play() { // if (runner != null) { // stop(); // } play = true; // runner = new Thread(this); // runner.start(); } /** * Begin playing the movie, with repeat. */ public void loop() { play(); repeat = true; } /** * Shut off the repeating loop. */ public void noLoop() { repeat = false; } /** * Pause the movie at its current time. */ public void pause() { play = false; //System.out.println("pause"); } /** * Stop the movie, and rewind. */ public void stop() { play = false; // runner = null; try { movie.setTimeValue(0); } catch (StdQTException e) { errorMessage("stop", e); } } /** * Set how often new frames are to be read from the movie. * Does not actually set the speed of the movie playback, * that's handled by the speed() method. */ public void frameRate(int ifps) { if (ifps <= 0) { System.err.println("Movie: ignoring bad frame rate of " + ifps + " fps."); } else { fps = ifps; } } /** * Set a multiplier for how fast/slow the movie should be run. * The default is 1.0. * <UL> * <LI>speed(2) will play the movie at double speed (2x). * <LI>speed(0.5) will play at half speed. * <LI>speed(-1) will play backwards at regular speed. * </UL> */ public void speed(float rate) { //rate = irate; try { movie.setRate(rate); } catch (StdQTException e) { errorMessage("speed", e); } } /** * Return the current time in seconds. * The number is a float so fractions of seconds can be used. */ public float time() { try { return (float)movie.getTime() / (float)movie.getTimeScale(); } catch (StdQTException e) { errorMessage("time", e); } return -1; } /** * Jump to a specific location (in seconds). * The number is a float so fractions of seconds can be used. */ public void jump(float where) { try { //movie.setTime(new TimeRecord(rate, where)); // scale, value //movie.setTime(new TimeRecord(1, where)); // scale, value int scaledTime = (int) (where * movie.getTimeScale()); movie.setTimeValue(scaledTime); } catch (StdQTException e) { errorMessage("jump", e); } } /** * Get the full length of this movie (in seconds). */ public float duration() { try { return (float)movie.getDuration() / (float)movie.getTimeScale(); } catch (StdQTException e) { errorMessage("length", e); } return -1; } /* public void play() { if(!play) { play = true; } start(); while( image == null) { try { Thread.sleep(5); } catch (InterruptedException e) { } } pixels = image.pixels; width = image.width; height = image.height; } public void repeat() { loop = true; if(!play) { play = true; } start(); while( image == null) { try { Thread.sleep(5); } catch (InterruptedException e) { } } pixels = image.pixels; width = image.width; height = image.height; } public void pause() { play = false; } */ public void run() { //System.out.println("entering thread"); while (Thread.currentThread() == runner) { //System.out.print("<"); try { //Thread.sleep(5); Thread.sleep(1000 / fps); } catch (InterruptedException e) { } //System.out.print(">"); // this could be a lie, but.. if (play) { //read(); //System.out.println("play"); available = true; if (movieEventMethod == null) { // If no special handling, then automatically read from the movie. read(); } else { try { movieEventMethod.invoke(parent, new Object[] { this }); } catch (Exception e) { System.err.println("error, disabling movieEvent() for " + filename); e.printStackTrace(); movieEventMethod = null; } } try { if (movie.isDone() && repeat) { movie.goToBeginning(); } } catch (StdQTException e) { play = false; errorMessage("rewinding", e); } //} else { //System.out.println("no play"); } //try { //read(); //if (movie.isDone() && loop) movie.goToBeginning(); //} catch (QTException e) { //System.err.println("Movie exception"); //e.printStackTrace(); //QTSession.close(); ?? //} } } /** * Call this to halt the movie from running, and stop its thread. */ public void dispose() { stop(); runner = null; QTSession.close(); } /** * General error reporting, all corraled here just in case * I think of something slightly more intelligent to do. */ protected void errorMessage(String where, Exception e) { parent.die("Error inside Movie." + where + "()", e); } }