Use ffmpeg for video exporting (default webm)
Remove monte media lib
This commit is contained in:
Binary file not shown.
@@ -192,16 +192,18 @@ public class ReplayMod {
|
|||||||
|
|
||||||
final String path = System.getProperty("replaymod.render.path");
|
final String path = System.getProperty("replaymod.render.path");
|
||||||
String type = System.getProperty("replaymod.render.type");
|
String type = System.getProperty("replaymod.render.type");
|
||||||
String quality = System.getProperty("replaymod.render.quality");
|
String bitrate = System.getProperty("replaymod.render.bitrate");
|
||||||
String fps = System.getProperty("replaymod.render.fps");
|
String fps = System.getProperty("replaymod.render.fps");
|
||||||
String waitForChunks = System.getProperty("replaymod.render.waitforchunks");
|
String waitForChunks = System.getProperty("replaymod.render.waitforchunks");
|
||||||
String linearMovement = System.getProperty("replaymod.render.linearmovement");
|
String linearMovement = System.getProperty("replaymod.render.linearmovement");
|
||||||
String skyColor = System.getProperty("replaymod.render.skycolor");
|
String skyColor = System.getProperty("replaymod.render.skycolor");
|
||||||
String width = System.getProperty("replaymod.render.width");
|
String width = System.getProperty("replaymod.render.width");
|
||||||
String height = System.getProperty("replaymod.render.height");
|
String height = System.getProperty("replaymod.render.height");
|
||||||
|
String exportCommand = System.getProperty("replaymod.render.exportcommand");
|
||||||
|
String exportCommandArgs = System.getProperty("replaymod.render.exportcommandargs");
|
||||||
final RenderOptions options = new RenderOptions();
|
final RenderOptions options = new RenderOptions();
|
||||||
if (quality != null) {
|
if (bitrate != null) {
|
||||||
options.setQuality(Float.parseFloat(quality));
|
options.setBitrate(bitrate);
|
||||||
}
|
}
|
||||||
if (fps != null) {
|
if (fps != null) {
|
||||||
options.setFps(Integer.parseInt(fps));
|
options.setFps(Integer.parseInt(fps));
|
||||||
@@ -226,6 +228,13 @@ public class ReplayMod {
|
|||||||
options.setHeight(Integer.parseInt(height));
|
options.setHeight(Integer.parseInt(height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exportCommand != null) {
|
||||||
|
options.setExportCommand(exportCommand);
|
||||||
|
}
|
||||||
|
if (exportCommandArgs != null) {
|
||||||
|
options.setExportCommandArgs(exportCommandArgs);
|
||||||
|
}
|
||||||
|
|
||||||
FrameRenderer renderer;
|
FrameRenderer renderer;
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
String[] parts = type.split(":");
|
String[] parts = type.split(":");
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ public class GuiRenderSettings extends GuiScreen {
|
|||||||
options.setFps(framerateSlider.getFPS());
|
options.setFps(framerateSlider.getFPS());
|
||||||
ReplayMod.replaySettings.setVideoFramerate(framerateSlider.getFPS());
|
ReplayMod.replaySettings.setVideoFramerate(framerateSlider.getFPS());
|
||||||
|
|
||||||
options.setQuality(qualitySlider.getQuality());
|
//TODO options.setQuality(qualitySlider.getQuality());
|
||||||
ReplayMod.replaySettings.setVideoQuality(qualitySlider.getQuality());
|
ReplayMod.replaySettings.setVideoQuality(qualitySlider.getQuality());
|
||||||
|
|
||||||
if(enableGreenscreen.isChecked()) {
|
if(enableGreenscreen.isChecked()) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import static org.apache.commons.lang3.Validate.*;
|
|||||||
|
|
||||||
public final class RenderOptions {
|
public final class RenderOptions {
|
||||||
private FrameRenderer renderer;
|
private FrameRenderer renderer;
|
||||||
private float quality = 0.5f;
|
private String bitrate = "10M";
|
||||||
private int fps = 30;
|
private int fps = 30;
|
||||||
|
|
||||||
// Advanced
|
// Advanced
|
||||||
@@ -18,6 +18,10 @@ public final class RenderOptions {
|
|||||||
private int height = Minecraft.getMinecraft().displayHeight;
|
private int height = Minecraft.getMinecraft().displayHeight;
|
||||||
|
|
||||||
// Highly advanced
|
// Highly advanced
|
||||||
|
private String exportCommand = "ffmpeg";
|
||||||
|
private String exportCommandArgs = "-f rawvideo -pix_fmt argb -s %WIDTH%x%HEIGHT% -r %FPS% -i - " +
|
||||||
|
"-an " +
|
||||||
|
"-c:v libvpx -b:v %BITRATE% %FILENAME%.webm";
|
||||||
private int writerQueueSize = Integer.parseInt(System.getProperty("replaymod.render.writerQueueSize", "1"));
|
private int writerQueueSize = Integer.parseInt(System.getProperty("replaymod.render.writerQueueSize", "1"));
|
||||||
|
|
||||||
public FrameRenderer getRenderer() {
|
public FrameRenderer getRenderer() {
|
||||||
@@ -28,13 +32,12 @@ public final class RenderOptions {
|
|||||||
this.renderer = notNull(renderer);
|
this.renderer = notNull(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getQuality() {
|
public String getBitrate() {
|
||||||
return quality;
|
return bitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setQuality(float quality) {
|
public void setBitrate(String bitrate) {
|
||||||
inclusiveBetween(0f, 1f, quality);
|
this.bitrate = bitrate;
|
||||||
this.quality = quality;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFps() {
|
public int getFps() {
|
||||||
@@ -90,6 +93,22 @@ public final class RenderOptions {
|
|||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getExportCommand() {
|
||||||
|
return exportCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportCommand(String exportCommand) {
|
||||||
|
this.exportCommand = exportCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExportCommandArgs() {
|
||||||
|
return exportCommandArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportCommandArgs(String exportCommandArgs) {
|
||||||
|
this.exportCommandArgs = exportCommandArgs;
|
||||||
|
}
|
||||||
|
|
||||||
public int getWriterQueueSize() {
|
public int getWriterQueueSize() {
|
||||||
return writerQueueSize;
|
return writerQueueSize;
|
||||||
}
|
}
|
||||||
@@ -101,13 +120,15 @@ public final class RenderOptions {
|
|||||||
public RenderOptions copy() {
|
public RenderOptions copy() {
|
||||||
RenderOptions copy = new RenderOptions();
|
RenderOptions copy = new RenderOptions();
|
||||||
copy.renderer = this.renderer;
|
copy.renderer = this.renderer;
|
||||||
copy.quality = this.quality;
|
copy.bitrate = this.bitrate;
|
||||||
copy.fps = this.fps;
|
copy.fps = this.fps;
|
||||||
copy.waitForChunks = this.waitForChunks;
|
copy.waitForChunks = this.waitForChunks;
|
||||||
copy.isLinearMovement = this.isLinearMovement;
|
copy.isLinearMovement = this.isLinearMovement;
|
||||||
copy.skyColor = this.skyColor;
|
copy.skyColor = this.skyColor;
|
||||||
copy.width = this.width;
|
copy.width = this.width;
|
||||||
copy.height = this.height;
|
copy.height = this.height;
|
||||||
|
copy.exportCommand = this.exportCommand;
|
||||||
|
copy.exportCommandArgs = this.exportCommandArgs;
|
||||||
copy.writerQueueSize = this.writerQueueSize;
|
copy.writerQueueSize = this.writerQueueSize;
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package eu.crushedpixel.replaymod.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class StreamPipe extends Thread {
|
||||||
|
private final InputStream in;
|
||||||
|
private final OutputStream out;
|
||||||
|
|
||||||
|
public StreamPipe(InputStream in, OutputStream out) {
|
||||||
|
super("StreamPipe from " + in + " to " + out);
|
||||||
|
this.in = in;
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
IOUtils.copy(in, out);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// We don't care
|
||||||
|
// Note: Once we use this for something important, we should probably care!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@ package eu.crushedpixel.replaymod.utils;
|
|||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
public class StringUtils {
|
public class StringUtils {
|
||||||
|
|
||||||
@@ -34,4 +36,81 @@ public class StringUtils {
|
|||||||
|
|
||||||
return rows.toArray(new String[rows.size()]);
|
return rows.toArray(new String[rows.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slightly modified from apache commons exec. Licensed under the Apache License, Version 2.0
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Crack a command line.
|
||||||
|
*
|
||||||
|
* @param toProcess
|
||||||
|
* the command line to process
|
||||||
|
* @return the command line broken into strings. An empty or null toProcess
|
||||||
|
* parameter results in a zero sized array
|
||||||
|
*/
|
||||||
|
public static List<String> translateCommandline(final String toProcess) {
|
||||||
|
if (toProcess == null || toProcess.length() == 0) {
|
||||||
|
// no command? no string
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse with a simple finite state machine
|
||||||
|
|
||||||
|
final int normal = 0;
|
||||||
|
final int inQuote = 1;
|
||||||
|
final int inDoubleQuote = 2;
|
||||||
|
int state = normal;
|
||||||
|
final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
|
||||||
|
final ArrayList<String> list = new ArrayList<String>();
|
||||||
|
StringBuilder current = new StringBuilder();
|
||||||
|
boolean lastTokenHasBeenQuoted = false;
|
||||||
|
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
final String nextTok = tok.nextToken();
|
||||||
|
switch (state) {
|
||||||
|
case inQuote:
|
||||||
|
if ("\'".equals(nextTok)) {
|
||||||
|
lastTokenHasBeenQuoted = true;
|
||||||
|
state = normal;
|
||||||
|
} else {
|
||||||
|
current.append(nextTok);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case inDoubleQuote:
|
||||||
|
if ("\"".equals(nextTok)) {
|
||||||
|
lastTokenHasBeenQuoted = true;
|
||||||
|
state = normal;
|
||||||
|
} else {
|
||||||
|
current.append(nextTok);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if ("\'".equals(nextTok)) {
|
||||||
|
state = inQuote;
|
||||||
|
} else if ("\"".equals(nextTok)) {
|
||||||
|
state = inDoubleQuote;
|
||||||
|
} else if (" ".equals(nextTok)) {
|
||||||
|
if (lastTokenHasBeenQuoted || current.length() != 0) {
|
||||||
|
list.add(current.toString());
|
||||||
|
current = new StringBuilder();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current.append(nextTok);
|
||||||
|
}
|
||||||
|
lastTokenHasBeenQuoted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTokenHasBeenQuoted || current.length() != 0) {
|
||||||
|
list.add(current.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == inQuote || state == inDoubleQuote) {
|
||||||
|
throw new IllegalArgumentException("Unbalanced quotes in "
|
||||||
|
+ toProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class VideoRenderer {
|
|||||||
|
|
||||||
public VideoRenderer(RenderOptions options) throws IOException {
|
public VideoRenderer(RenderOptions options) throws IOException {
|
||||||
this.frameRenderer = options.getRenderer();
|
this.frameRenderer = options.getRenderer();
|
||||||
this.videoWriter = new VideoWriter(options);
|
this.videoWriter = new VideoWriter(this, options);
|
||||||
this.gui = new GuiVideoRenderer(this);
|
this.gui = new GuiVideoRenderer(this);
|
||||||
this.replaySender = ReplayMod.replaySender;
|
this.replaySender = ReplayMod.replaySender;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|||||||
@@ -3,33 +3,41 @@ package eu.crushedpixel.replaymod.video;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||||
import org.apache.commons.io.FileUtils;
|
import eu.crushedpixel.replaymod.utils.StreamPipe;
|
||||||
import org.monte.media.*;
|
import eu.crushedpixel.replaymod.utils.StringUtils;
|
||||||
import org.monte.media.FormatKeys.MediaType;
|
import net.minecraft.client.Minecraft;
|
||||||
import org.monte.media.math.Rational;
|
import net.minecraft.crash.CrashReport;
|
||||||
|
import net.minecraft.crash.CrashReportCategory;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.awt.image.DataBufferInt;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.*;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.Validate.isTrue;
|
||||||
|
|
||||||
public class VideoWriter {
|
public class VideoWriter {
|
||||||
|
|
||||||
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
|
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
|
||||||
private static final SimpleDateFormat FILE_FORMAT = new SimpleDateFormat(DATE_FORMAT);
|
private static final SimpleDateFormat FILE_FORMAT = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
|
||||||
private static final String VIDEO_EXTENSION = ".avi";
|
private final RenderOptions options;
|
||||||
|
private final Process process;
|
||||||
private final File file;
|
private final OutputStream outputStream;
|
||||||
private final MovieWriter out;
|
private final WritableByteChannel channel;
|
||||||
private final Buffer buf;
|
|
||||||
private final int track;
|
|
||||||
|
|
||||||
private volatile boolean active = true;
|
private volatile boolean active = true;
|
||||||
private volatile boolean cancelled = false;
|
private volatile boolean cancelled = false;
|
||||||
@@ -43,68 +51,81 @@ public class VideoWriter {
|
|||||||
private final Condition noLongerEmptyCondition = lock.newCondition();
|
private final Condition noLongerEmptyCondition = lock.newCondition();
|
||||||
private final Condition noLongerFullCondition = lock.newCondition();
|
private final Condition noLongerFullCondition = lock.newCondition();
|
||||||
|
|
||||||
public VideoWriter(RenderOptions options) throws IOException {
|
public VideoWriter(final VideoRenderer renderer, final RenderOptions options) throws IOException {
|
||||||
|
this.options = options.copy();
|
||||||
this.queueLimit = options.getWriterQueueSize();
|
this.queueLimit = options.getWriterQueueSize();
|
||||||
this.toWrite = new LinkedList<BufferedImage>();
|
this.toWrite = new LinkedList<BufferedImage>();
|
||||||
|
|
||||||
File folder = ReplayFileIO.getRenderFolder();
|
File folder = ReplayFileIO.getRenderFolder();
|
||||||
String fileName = FILE_FORMAT.format(Calendar.getInstance().getTime());
|
String fileName = FILE_FORMAT.format(Calendar.getInstance().getTime());
|
||||||
file = new File(folder, fileName + VIDEO_EXTENSION);
|
|
||||||
FileUtils.touch(file);
|
|
||||||
|
|
||||||
out = Registry.getInstance().getWriter(file);
|
final String args = options.getExportCommandArgs()
|
||||||
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
|
.replace("%WIDTH%", String.valueOf(options.getWidth()))
|
||||||
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
|
.replace("%HEIGHT%", String.valueOf(options.getHeight()))
|
||||||
FormatKeys.FrameRateKey, new Rational(options.getFps(), 1),
|
.replace("%FPS%", String.valueOf(options.getFps()))
|
||||||
VideoFormatKeys.WidthKey, options.getWidth(),
|
.replace("%FILENAME%", fileName)
|
||||||
VideoFormatKeys.HeightKey, options.getHeight(),
|
.replace("%BITRATE%", options.getBitrate());
|
||||||
VideoFormatKeys.DepthKey, 24,
|
|
||||||
VideoFormatKeys.QualityKey, options.getQuality());
|
|
||||||
|
|
||||||
track = out.addTrack(format);
|
List<String> command = new ArrayList<String>();
|
||||||
|
command.add(options.getExportCommand());
|
||||||
buf = new Buffer();
|
command.addAll(StringUtils.translateCommandline(args));
|
||||||
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
|
System.out.println("Starting " + options.getExportCommand() + " with args: " + args);
|
||||||
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
|
process = new ProcessBuilder(command).directory(folder).start();
|
||||||
|
OutputStream exportLogOut = new FileOutputStream("export.log");
|
||||||
|
new StreamPipe(process.getInputStream(), exportLogOut).start();
|
||||||
|
new StreamPipe(process.getErrorStream(), exportLogOut).start();
|
||||||
|
outputStream = process.getOutputStream();
|
||||||
|
channel = Channels.newChannel(outputStream);
|
||||||
|
|
||||||
writerThread = new Thread(new Runnable() {
|
writerThread = new Thread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
while(!cancelled && (active || !toWrite.isEmpty())) {
|
|
||||||
try {
|
|
||||||
lock.lockInterruptibly();
|
|
||||||
BufferedImage img;
|
|
||||||
try {
|
|
||||||
img = toWrite.poll();
|
|
||||||
if (img == null) {
|
|
||||||
noLongerEmptyCondition.await();
|
|
||||||
img = toWrite.poll();
|
|
||||||
}
|
|
||||||
noLongerFullCondition.signal();
|
|
||||||
if (toWrite.isEmpty()) {
|
|
||||||
emptyCondition.signalAll();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
buf.data = img;
|
|
||||||
try {
|
|
||||||
out.write(track, buf);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
toWrite.clear();
|
while (!cancelled && (active || !toWrite.isEmpty())) {
|
||||||
out.close();
|
try {
|
||||||
if (cancelled) {
|
lock.lockInterruptibly();
|
||||||
FileUtils.forceDelete(file);
|
BufferedImage img;
|
||||||
|
try {
|
||||||
|
img = toWrite.poll();
|
||||||
|
if (img == null) {
|
||||||
|
noLongerEmptyCondition.await();
|
||||||
|
img = toWrite.poll();
|
||||||
|
}
|
||||||
|
noLongerFullCondition.signal();
|
||||||
|
if (toWrite.isEmpty()) {
|
||||||
|
emptyCondition.signalAll();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
DataBuffer imgBuffer = img.getRaster().getDataBuffer();
|
||||||
|
if (imgBuffer instanceof DataBufferByte) {
|
||||||
|
outputStream.write(((DataBufferByte) imgBuffer).getData());
|
||||||
|
} else if (imgBuffer instanceof DataBufferInt) {
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.allocate(img.getWidth() * img.getHeight() * 4);
|
||||||
|
byteBuffer.asIntBuffer().put(((DataBufferInt) imgBuffer).getData());
|
||||||
|
channel.write(byteBuffer);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("DataBuffer type not supported: " + imgBuffer.getClass());
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
toWrite.clear();
|
||||||
|
IOUtils.closeQuietly(outputStream);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
if (active) {
|
||||||
|
CrashReport report = new CrashReport("Exporting frame", e);
|
||||||
|
CrashReportCategory exportDetails = report.makeCategory("Export details");
|
||||||
|
exportDetails.addCrashSection("Export command", options.getExportCommand());
|
||||||
|
exportDetails.addCrashSection("Export args", args);
|
||||||
|
Minecraft.getMinecraft().crashed(report);
|
||||||
|
renderer.cancel();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
process.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, "replaymod-video-writer");
|
}, "replaymod-video-writer");
|
||||||
@@ -124,6 +145,8 @@ public class VideoWriter {
|
|||||||
*/
|
*/
|
||||||
public boolean writeImage(BufferedImage image, boolean waitIfFull) {
|
public boolean writeImage(BufferedImage image, boolean waitIfFull) {
|
||||||
Preconditions.checkState(active, "This VideoWriter has already been closed.");
|
Preconditions.checkState(active, "This VideoWriter has already been closed.");
|
||||||
|
isTrue(image.getWidth() == options.getWidth(), "Width has to be " + options.getWidth() + " but was " + image.getWidth());
|
||||||
|
isTrue(image.getHeight() == options.getHeight(), "Height has to be " + options.getHeight() + " but was " + image.getHeight());
|
||||||
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user