Merge pull request #3635 from GKFX/moviemkr-badframes

Allow Movie Maker to cope with bad files.
This commit is contained in:
Ben Fry
2015-08-16 11:39:30 -04:00

View File

@@ -35,11 +35,11 @@ import ch.randelshofer.media.quicktime.QuickTimeWriter;
* Hacked from Werner Randelshofer's QuickTimeWriter demo. The original version
* can be found <a href="http://www.randelshofer.ch/blog/2010/10/writing-quicktime-movies-in-pure-java/">here</a>.
* <p>
* A more up-to-date version of the project seems to be
* A more up-to-date version of the project is
* <a href="http://www.randelshofer.ch/monte/">here</a>.
* If someone would like to help us update the encoder, that'd be great.
* Problem is, it's too big, so we don't want to merge it into our code.
* <p>
* Broken out as a separate project because the license (CC) probably isn't
* Broken out as a separate project because the license (CC) probably isn't
* compatible with the rest of Processing and we don't want any confusion.
* <p>
* Added JAI ImageIO to support lots of other image file formats [131008].
@@ -52,7 +52,7 @@ import ch.randelshofer.media.quicktime.QuickTimeWriter;
* <li> The dialog box is super ugly. It's a hacked up version of the previous
* interface, but I'm too scared to pull that GUI layout code apart.
* <li> The 'None' compressor seems to have bugs, so just disabled it instead.
* <li> The 'pass through' option seems to be broken, so it's been removed.
* <li> The 'pass through' option seems to be broken, so it's been removed.
* In its place is an option to use the same width/height as the originals.
* <li> When this new 'pass through' is set, there's some nastiness with how
* the 'final' width/height variables are passed to the movie maker.
@@ -228,10 +228,10 @@ public class MovieMaker extends JFrame implements Tool {
chooseImageFolderButton.setText(Language.text("movie_maker.choose_button"));
//chooseImageFolderButton.addActionListener(formListener);
chooseImageFolderButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Chooser.selectFolder(MovieMaker.this,
Chooser.selectFolder(MovieMaker.this,
Language.text("movie_maker.select_image_folder"),
new File(imageFolderField.getText()),
new Chooser.Callback() {
@@ -249,10 +249,10 @@ public class MovieMaker extends JFrame implements Tool {
chooseSoundFileButton.setText(Language.text("movie_maker.choose_button"));
//chooseSoundFileButton.addActionListener(formListener);
chooseSoundFileButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Chooser.selectInput(MovieMaker.this,
Chooser.selectInput(MovieMaker.this,
Language.text("movie_maker.select_sound_file"),
new File(soundFileField.getText()),
new Chooser.Callback() {
@@ -269,14 +269,14 @@ public class MovieMaker extends JFrame implements Tool {
createMovieButton.setText(Language.text("movie_maker.create_movie_button"));
// createMovieButton.addActionListener(formListener);
createMovieButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String lastPath = prefs.get("movie.outputFile", null);
File lastFile = lastPath == null ? null : new File(lastPath);
Chooser.selectOutput(MovieMaker.this,
Chooser.selectOutput(MovieMaker.this,
Language.text("movie_maker.save_dialog_prompt"),
lastFile,
lastFile,
new Chooser.Callback() {
@Override
void select(File file) {
@@ -295,7 +295,7 @@ public class MovieMaker extends JFrame implements Tool {
// public void run() {
// createMovie(target);
// }
//
//
// });
}
}
@@ -616,16 +616,23 @@ public class MovieMaker extends JFrame implements Tool {
Arrays.sort(imgFiles);
}
// Check on first image, if we can actually do pass through
// Get the width and height if we're preserving size.
// Nullify bad Files so we don't get errors twice.
if (originalSize) {
BufferedImage temp = readImage(imgFiles[0]);
if (temp == null) {
return new RuntimeException(Language.interpolate("movie_maker.error.cant_read", imgFiles[0].getAbsolutePath()));
}
width = temp.getWidth();
height = temp.getHeight();
if (width <= 0 || height <= 0) {
return new RuntimeException(Language.interpolate("movie_maker.error.cant_read_maybe_bad", imgFiles[0].getName()));
for (int i = 0; true; ++i) {
// No images at all? No video then.
if (i >= imgFiles.length) {
throw new RuntimeException(Language.text("movie_maker.error.no_images_found"));
}
BufferedImage temp = readImage(imgFiles[i]);
if (temp == null) {
imgFiles[i] = null;
} else {
width = temp.getWidth();
height = temp.getHeight();
break;
}
}
}
@@ -642,7 +649,7 @@ public class MovieMaker extends JFrame implements Tool {
writeAudioOnly(movieFile, soundFile, streaming);
}
return null;
} catch (Throwable t) {
return t;
}
@@ -673,50 +680,89 @@ public class MovieMaker extends JFrame implements Tool {
}//GEN-LAST:event_createMovie
/**
/**
* Read an image from a file. ImageIcon doesn't don't do well with some file
* types, so we use ImageIO. ImageIO doesn't handle TGA files created by
* Processing, so this calls our own loadImageTGA().
* <br> Prints errors itself.
* @return null on error; image only if okay.
*/
private BufferedImage readImage(File file) throws IOException {
// Make sure that we're using a ClassLoader that's aware of the ImageIO jar
//Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
//BufferedImage image = ImageIO.read(file);
// rewritten to switch back to the default loader
Thread current = Thread.currentThread();
ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
current.setContextClassLoader(getClass().getClassLoader());
BufferedImage image = ImageIO.read(file);
current.setContextClassLoader(origLoader);
private BufferedImage readImage(File file) {
try {
// Make sure that we're using a ClassLoader that's aware of the ImageIO jar
//Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
//BufferedImage image = ImageIO.read(file);
// rewritten to switch back to the default loader
Thread current = Thread.currentThread();
ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
current.setContextClassLoader(getClass().getClassLoader());
/*
String[] loadImageFormats = ImageIO.getReaderFormatNames();
if (loadImageFormats != null) {
for (String format : loadImageFormats) {
System.out.println(format);
BufferedImage image;
try {
image = ImageIO.read(file);
} catch (IOException e) {
System.err.println(Language.interpolate("movie_maker.error.cant_read",
file.getAbsolutePath()));
return null;
}
}
*/
if (image == null) {
String path = file.getAbsolutePath();
String pathLower = path.toLowerCase();
// Might be an incompatible TGA or TIFF created by Processing
if (pathLower.endsWith(".tga")) {
return loadImageTGA(file);
} else if (pathLower.endsWith(".tif") || pathLower.endsWith(".tiff")) {
throw new IOException(Language.text("movie_maker.error.avoid_tiff"));
current.setContextClassLoader(origLoader);
/*
String[] loadImageFormats = ImageIO.getReaderFormatNames();
if (loadImageFormats != null) {
for (String format : loadImageFormats) {
System.out.println(format);
}
}
*/
if (image == null) {
String path = file.getAbsolutePath();
String pathLower = path.toLowerCase();
// Might be an incompatible TGA or TIFF created by Processing
if (pathLower.endsWith(".tga")) {
try {
return loadImageTGA(file);
} catch (IOException e) {
System.err.println(Language.interpolate("movie_maker.error.cant_read",
file.getAbsolutePath()));
return null;
}
} else if (pathLower.endsWith(".tif") || pathLower.endsWith(".tiff")) {
System.err.println(Language.interpolate("movie_maker.error.cant_read",
file.getAbsolutePath()) + " " +
Language.text("movie_maker.error.avoid_tiff"));
return null;
} else {
System.err.println(Language.interpolate("movie_maker.error.cant_read",
file.getAbsolutePath()));
return null;
}
} else {
if (image.getWidth() <= 0 || image.getHeight() <= 0) {
System.err.println(Language.interpolate("movie_maker.error.cant_read_maybe_bad",
file.getAbsolutePath()));
return null;
}
}
return image;
// Catch-all is sometimes needed.
} catch (RuntimeException e) {
System.err.println(Language.interpolate("movie_maker.error.cant_read", file.getAbsolutePath()));
return null;
}
return image;
}
/** variable frame rate. */
private void writeVideoOnlyVFR(File movieFile, File[] imgFiles, int width, int height, double fps, QuickTimeWriter.VideoFormat videoFormat, /*boolean passThrough,*/ String streaming) throws IOException {
File tmpFile = streaming.equals("none") ? movieFile : new File(movieFile.getPath() + ".tmp");
ProgressMonitor p = new ProgressMonitor(MovieMaker.this,
ProgressMonitor p = new ProgressMonitor(MovieMaker.this,
Language.interpolate("movie_maker.progress.creating_file_name", movieFile.getName()),
Language.text("movie_maker.progress.creating_output_file"),
0, imgFiles.length);
@@ -746,6 +792,8 @@ public class MovieMaker extends JFrame implements Tool {
int prevImgDuration = 0;
for (int i = 0; i < imgFiles.length && !p.isCanceled(); i++) {
File f = imgFiles[i];
if (f == null) continue;
p.setNote(Language.interpolate("movie_maker.progress.processing", f.getName()));
p.setProgress(i);
@@ -755,6 +803,8 @@ public class MovieMaker extends JFrame implements Tool {
} else {
//BufferedImage fImg = ImageIO.read(f);
BufferedImage fImg = readImage(f);
if (fImg == null) continue;
g.drawImage(fImg, 0, 0, width, height, null);
if (i != 0 && Arrays.equals(data, prevData)) {
prevImgDuration += duration;
@@ -871,6 +921,7 @@ public class MovieMaker extends JFrame implements Tool {
private void writeVideoAndAudio(File movieFile, File[] imgFiles, File audioFile,
int width, int height, double fps, QuickTimeWriter.VideoFormat videoFormat,
/*boolean passThrough,*/ String streaming) throws IOException {
File tmpFile = streaming.equals("none") ? movieFile : new File(movieFile.getPath() + ".tmp");
ProgressMonitor p = new ProgressMonitor(MovieMaker.this,
Language.interpolate("movie_maker.progress.creating_file_name", movieFile.getName()),
@@ -949,24 +1000,25 @@ public class MovieMaker extends JFrame implements Tool {
}
// Advance video to movie time
while (imgIndex < imgFiles.length && qtOut.getTrackDuration(1) < movieTime) {
for (; imgIndex < imgFiles.length && qtOut.getTrackDuration(1) < movieTime; ++imgIndex) {
// catch up with video time
p.setProgress(imgIndex);
p.setNote(Language.interpolate("movie_maker.progress.processing", imgFiles[imgIndex].getName()));
File f = imgFiles[imgIndex];
if (f == null) continue;
p.setNote(Language.interpolate("movie_maker.progress.processing", f.getName()));
//if (passThrough) {
if (false) {
qtOut.writeSample(1, imgFiles[imgIndex], vsDuration);
qtOut.writeSample(1, f, vsDuration);
} else {
//BufferedImage fImg = ImageIO.read(imgFiles[imgIndex]);
BufferedImage fImg = readImage(imgFiles[imgIndex]);
if (fImg == null) {
continue;
}
BufferedImage fImg = readImage(f);
if (fImg == null) continue;
g.drawImage(fImg, 0, 0, width, height, null);
fImg.flush();
qtOut.writeFrame(1, imgBuffer, vsDuration);
}
++imgIndex;
}
}
if (streaming.equals("fastStart")) {
@@ -999,14 +1051,14 @@ public class MovieMaker extends JFrame implements Tool {
}
}
/**
* Targa image loader for RLE-compressed TGA files.
* Code taken from PApplet, any changes here should lead to updates there.
* Targa image loader for RLE-compressed TGA files.
* Code taken from PApplet, any changes here should lead to updates there.
*/
static private BufferedImage loadImageTGA(File file) throws IOException {
InputStream is = new FileInputStream(file);
try {
byte header[] = new byte[18];
int offset = 0;
@@ -1168,7 +1220,7 @@ public class MovieMaker extends JFrame implements Tool {
break;
case ARGB:
for (int i = 0; i < num; i++) {
pixels[index++] = is.read() |
pixels[index++] = is.read() |
(is.read() << 8) | (is.read() << 16) | (is.read() << 24);
}
break;
@@ -1193,13 +1245,13 @@ public class MovieMaker extends JFrame implements Tool {
WritableRaster wr = image.getRaster();
wr.setDataElements(0, 0, w, h, pixels);
return image;
} finally {
is.close();
}
}
/**
* @param args the command line arguments
*/
@@ -1214,7 +1266,7 @@ public class MovieMaker extends JFrame implements Tool {
}
});
}
private JLabel aboutLabel;
private JButton chooseImageFolderButton;