Files
processing4/video/Movie.java
2005-04-04 22:53:45 +00:00

572 lines
14 KiB
Java

/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
PMovie - reading from video files
Part of the Processing project - http://processing.org
Copyright (c) 2004 Ben Fry
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.awt.event.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import quicktime.*;
import quicktime.io.QTFile;
import quicktime.qd.*;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.std.movies.media.DataRef;
import quicktime.util.RawEncodedImage;
public class Movie extends PImage
implements /*StdQTConstants, StdQTConstants4,*/ PConstants, Runnable {
PApplet parent;
Method movieEventMethod;
String filename;
Thread runner;
PImage borderImage;
boolean removeBorders = true;
boolean play;
boolean repeat;
boolean available;
//float rate;
int fps;
/** The 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();
}
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(PApplet parent, String filename, int ifps) {
//this(parent, parent.getClass().getResource("data/" + filename), ifps);
// this creates a fake image so that the first time this
// attempts to draw, something happens that's not an exception
super.init(1, 1, RGB);
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);
//movie = quicktime.std.movies.Movie.fromDataRef(urlRef,
//StdQTConstants4.newMovieAsyncOK |
//StdQTConstants.newMovieActive);
init(parent, movie, ifps);
return;
} catch (QTException qte) {
qte.printStackTrace();
return;
} catch (MalformedURLException e) {
e.printStackTrace();
return;
}
}
url = getClass().getResource(filename);
if (url != null) {
init(parent, url, ifps);
return;
}
url = getClass().getResource("data/" + filename);
if (url != null) {
init(parent, url, ifps);
return;
}
try {
try {
// look inside the sketch folder (if set)
String location = parent.folder + File.separator + "data";
File file = new File(location, filename);
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
//movie = quicktime.std.movies.Movie.fromDataRef(new DataRef(new QTFile(file)),
//StdQTConstants4.newMovieAsyncOK |
//StdQTConstants.newMovieActive);
init(parent, movie, ifps);
return;
}
} catch (QTException e) { } // ignored
try {
File file = new File("data", filename);
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
//movie = quicktime.std.movies.Movie.fromDataRef(new DataRef(new QTFile(file)),
//StdQTConstants4.newMovieAsyncOK |
//StdQTConstants.newMovieActive);
init(parent, movie, ifps);
return;
}
} catch (QTException e2) { }
try {
File file = new File(filename);
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
//movie = quicktime.std.movies.Movie.fromDataRef(new DataRef(new QTFile(file)),
//StdQTConstants4.newMovieAsyncOK |
//StdQTConstants.newMovieActive);
init(parent, movie, ifps);
return;
}
} catch (QTException e1) { }
} catch (SecurityException se) { } // online, whups
parent.die("Could not find movie file " + filename, null);
}
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) {
// qtjava likes file: urls to read file:/// not file:/
// so this changes them when appropriate
String externalized = url.toExternalForm();
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 {
DataRef urlRef = new DataRef(externalized);
movie = fromDataRef(urlRef);
//movie = quicktime.std.movies.Movie.fromDataRef(urlRef,
//StdQTConstants4.newMovieAsyncOK |
//StdQTConstants.newMovieActive);
init(parent, movie, ifps);
} catch (QTException e) {
e.printStackTrace();
}
//System.out.println("done with init");
}
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 {
movie.prePreroll(0, 1.0f);
movie.preroll(0, 1.0f);
while (movie.maxLoadedTimeInMovie() == 0) {
movie.task(100);
}
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);
}
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) {
int bpixels[] = new int[dataWidth * movieHeight];
borderImage = new PImage(bpixels, 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);
// 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
} catch (QTException qte) {
qte.printStackTrace();
QTSession.close();
}
}
/**
* Begin playing the movie, with no repeat.
*/
public void play() {
play = true;
}
/**
* Begin playing the movie, with repeat.
*/
public void loop() {
play = true;
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;
System.out.println("stop");
try {
movie.setTimeValue(0);
} catch (StdQTException e) {
errorMessage("stop", e);
}
}
/**
* Set how often new frames are to be read from the movie.
*/
public void framerate(int ifps) {
if (ifps <= 0) {
System.err.println("Movie: ignoring bad framerate of " +
ifps + " fps.");
return;
}
fps = ifps;
}
/**
* Set a multiplier for how fast/slow the movie should be run.
* speed(2) will play the movie at double speed (2x).
* speed(0.5) will play at half speed.
* speed(-1) will play backwards at regular speed.
*/
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(int where) {
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) {
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(); ??
//}
}
}
public void dispose() {
System.out.println("disposing");
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);
}
}