mirror of
https://github.com/processing/processing4.git
synced 2026-02-03 05:39:18 +01:00
572 lines
14 KiB
Java
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);
|
|
}
|
|
}
|
|
|