Use ffmpeg for video exporting (default webm)
Remove monte media lib
This commit is contained in:
@@ -55,7 +55,7 @@ public class VideoRenderer {
|
||||
|
||||
public VideoRenderer(RenderOptions options) throws IOException {
|
||||
this.frameRenderer = options.getRenderer();
|
||||
this.videoWriter = new VideoWriter(options);
|
||||
this.videoWriter = new VideoWriter(this, options);
|
||||
this.gui = new GuiVideoRenderer(this);
|
||||
this.replaySender = ReplayMod.replaySender;
|
||||
this.options = options;
|
||||
|
||||
@@ -3,33 +3,41 @@ package eu.crushedpixel.replaymod.video;
|
||||
import com.google.common.base.Preconditions;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.monte.media.*;
|
||||
import org.monte.media.FormatKeys.MediaType;
|
||||
import org.monte.media.math.Rational;
|
||||
import eu.crushedpixel.replaymod.utils.StreamPipe;
|
||||
import eu.crushedpixel.replaymod.utils.StringUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
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.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
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.util.Calendar;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static org.apache.commons.lang3.Validate.isTrue;
|
||||
|
||||
public class VideoWriter {
|
||||
|
||||
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 String VIDEO_EXTENSION = ".avi";
|
||||
|
||||
private final File file;
|
||||
private final MovieWriter out;
|
||||
private final Buffer buf;
|
||||
private final int track;
|
||||
private final RenderOptions options;
|
||||
private final Process process;
|
||||
private final OutputStream outputStream;
|
||||
private final WritableByteChannel channel;
|
||||
|
||||
private volatile boolean active = true;
|
||||
private volatile boolean cancelled = false;
|
||||
@@ -43,68 +51,81 @@ public class VideoWriter {
|
||||
private final Condition noLongerEmptyCondition = 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.toWrite = new LinkedList<BufferedImage>();
|
||||
|
||||
File folder = ReplayFileIO.getRenderFolder();
|
||||
String fileName = FILE_FORMAT.format(Calendar.getInstance().getTime());
|
||||
file = new File(folder, fileName + VIDEO_EXTENSION);
|
||||
FileUtils.touch(file);
|
||||
|
||||
out = Registry.getInstance().getWriter(file);
|
||||
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
|
||||
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
|
||||
FormatKeys.FrameRateKey, new Rational(options.getFps(), 1),
|
||||
VideoFormatKeys.WidthKey, options.getWidth(),
|
||||
VideoFormatKeys.HeightKey, options.getHeight(),
|
||||
VideoFormatKeys.DepthKey, 24,
|
||||
VideoFormatKeys.QualityKey, options.getQuality());
|
||||
final String args = options.getExportCommandArgs()
|
||||
.replace("%WIDTH%", String.valueOf(options.getWidth()))
|
||||
.replace("%HEIGHT%", String.valueOf(options.getHeight()))
|
||||
.replace("%FPS%", String.valueOf(options.getFps()))
|
||||
.replace("%FILENAME%", fileName)
|
||||
.replace("%BITRATE%", options.getBitrate());
|
||||
|
||||
track = out.addTrack(format);
|
||||
|
||||
buf = new Buffer();
|
||||
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
|
||||
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
|
||||
List<String> command = new ArrayList<String>();
|
||||
command.add(options.getExportCommand());
|
||||
command.addAll(StringUtils.translateCommandline(args));
|
||||
System.out.println("Starting " + options.getExportCommand() + " with args: " + args);
|
||||
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() {
|
||||
|
||||
@Override
|
||||
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 {
|
||||
toWrite.clear();
|
||||
out.close();
|
||||
if (cancelled) {
|
||||
FileUtils.forceDelete(file);
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
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");
|
||||
@@ -124,6 +145,8 @@ public class VideoWriter {
|
||||
*/
|
||||
public boolean writeImage(BufferedImage image, boolean waitIfFull) {
|
||||
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();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user