Added Video Export feature with according Options in the Options Menu
Fixed Linear Interpolation
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import eu.crushedpixel.replaymod.chat.ChatMessageRequests;
|
||||
import eu.crushedpixel.replaymod.chat.ChatMessageRequests.ChatMessageType;
|
||||
import eu.crushedpixel.replaymod.gui.GuiReplaySaving;
|
||||
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||
import eu.crushedpixel.replaymod.utils.ImageUtils;
|
||||
|
||||
public class ReplayScreenshot {
|
||||
|
||||
private static Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
private static final byte[] uniqueBytes = new byte[]{0,1,1,2,3,5,8};
|
||||
|
||||
private static long last_finish = -1;
|
||||
|
||||
private static boolean before;
|
||||
private static double beforeSpeed;
|
||||
private static GuiScreen beforeScreen;
|
||||
|
||||
public static void prepareScreenshot() {
|
||||
System.out.println("thumbnail preparing");
|
||||
before = mc.gameSettings.hideGUI;
|
||||
beforeSpeed = ReplayHandler.getSpeed();
|
||||
beforeScreen = mc.currentScreen;
|
||||
}
|
||||
|
||||
private static boolean locked = false;
|
||||
|
||||
public static void saveScreenshot(Framebuffer buffer) {
|
||||
|
||||
if(locked) return;
|
||||
locked = true;
|
||||
|
||||
System.out.println("thumbnail started");
|
||||
try {
|
||||
GuiReplaySaving.replaySaving = true;
|
||||
|
||||
mc.gameSettings.hideGUI = true;
|
||||
mc.currentScreen = null;
|
||||
|
||||
mc.entityRenderer.updateCameraAndRender(0);
|
||||
ReplayHandler.setSpeed(0);
|
||||
|
||||
final BufferedImage fbi = ScreenCapture.captureScreen();
|
||||
|
||||
mc.gameSettings.hideGUI = before;
|
||||
mc.currentScreen = beforeScreen;
|
||||
ReplayHandler.setSpeed(beforeSpeed);
|
||||
|
||||
//The actual cropping and saving should be executed in a separate thread
|
||||
Thread ioThread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("thumbnail saving started");
|
||||
float aspect = 1280f/720f;
|
||||
|
||||
Rectangle rect;
|
||||
if((float)fbi.getWidth()/(float)fbi.getHeight() <= aspect) {
|
||||
int h = Math.round(fbi.getWidth()/aspect);
|
||||
int y = (fbi.getHeight()/2) - (h/2);
|
||||
rect = new Rectangle(0, y, fbi.getWidth(), h);
|
||||
} else {
|
||||
int w = Math.round(fbi.getHeight()*aspect);
|
||||
int x = (fbi.getWidth()/2) - (w/2);
|
||||
rect = new Rectangle(x, 0, w, fbi.getHeight());
|
||||
}
|
||||
|
||||
final BufferedImage nbi = ImageUtils.cropImage(fbi, rect);
|
||||
|
||||
File replayFile = ReplayHandler.getReplayFile();
|
||||
|
||||
File folder = new File("./replay_recordings/");
|
||||
folder.mkdirs();
|
||||
|
||||
File temp = File.createTempFile("thumb", null);
|
||||
|
||||
int h = 720;
|
||||
int w = 1280;
|
||||
|
||||
BufferedImage img = ImageUtils.scaleImage(nbi, new Dimension(w, h));
|
||||
|
||||
ImageIO.write(img, "jpg", temp);
|
||||
|
||||
File tempFile = File.createTempFile(replayFile.getName(), null);
|
||||
tempFile.delete(); tempFile.deleteOnExit();
|
||||
|
||||
replayFile.renameTo(tempFile);
|
||||
|
||||
replayFile.delete();
|
||||
replayFile.createNewFile();
|
||||
|
||||
byte[] buf = new byte[1024];
|
||||
|
||||
ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
|
||||
ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(replayFile));
|
||||
|
||||
FileInputStream fis = new FileInputStream(temp);
|
||||
|
||||
ZipEntry entry = zin.getNextEntry();
|
||||
while (entry != null) {
|
||||
String name = entry.getName();
|
||||
|
||||
if(!name.contains("thumb")) {
|
||||
// Add ZIP entry to output stream.
|
||||
zout.putNextEntry(new ZipEntry(name));
|
||||
// Transfer bytes from the ZIP file to the output file
|
||||
int len;
|
||||
while ((len = zin.read(buf)) > 0) {
|
||||
zout.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
entry = zin.getNextEntry();
|
||||
}
|
||||
|
||||
zout.putNextEntry(new ZipEntry("thumb"));
|
||||
int len;
|
||||
//Add unique bytes to the end of the file
|
||||
zout.write(uniqueBytes);
|
||||
|
||||
while ((len = fis.read(buf)) > 0) {
|
||||
zout.write(buf, 0, len);
|
||||
}
|
||||
|
||||
// Close the streams
|
||||
fis.close();
|
||||
zin.close();
|
||||
// Compress the files
|
||||
// Complete the ZIP file
|
||||
zout.close();
|
||||
tempFile.delete();
|
||||
temp.delete();
|
||||
System.out.println("thumbnail saving finished");
|
||||
|
||||
ChatMessageRequests.addChatMessage("Thumbnail has been successfully saved", ChatMessageType.INFORMATION);
|
||||
} catch(Exception e) {}
|
||||
finally {
|
||||
GuiReplaySaving.replaySaving = false;
|
||||
locked = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ioThread.start();
|
||||
}
|
||||
catch (Exception exception) {
|
||||
mc.gameSettings.hideGUI = before;
|
||||
mc.currentScreen = beforeScreen;
|
||||
ReplayHandler.setSpeed(beforeSpeed);
|
||||
exception.printStackTrace();
|
||||
ChatMessageRequests.addChatMessage("Thumbnail could not be saved", ChatMessageType.WARNING);
|
||||
}
|
||||
|
||||
last_finish = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
||||
import net.minecraft.util.Timer;
|
||||
|
||||
public class ReplayTimer extends Timer {
|
||||
|
||||
public ReplayTimer(float p_i1018_1_) {
|
||||
super(p_i1018_1_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTimer() {
|
||||
if(ReplayProcess.isVideoRecording()) return;
|
||||
super.updateTimer();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Robot;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.monte.screenrecorder.ScreenRecorder;
|
||||
|
||||
public class ScreenCapture {
|
||||
|
||||
private static final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
private static IntBuffer pixelBuffer;
|
||||
private static int[] pixelValues;
|
||||
|
||||
public static BufferedImage captureScreen() {
|
||||
int width = mc.displayWidth;
|
||||
int height = mc.displayHeight;
|
||||
|
||||
Framebuffer buffer = mc.getFramebuffer();
|
||||
|
||||
if (OpenGlHelper.isFramebufferEnabled())
|
||||
{
|
||||
width = buffer.framebufferTextureWidth;
|
||||
height = buffer.framebufferTextureHeight;
|
||||
}
|
||||
|
||||
int k = width * height;
|
||||
|
||||
if (pixelBuffer == null || pixelBuffer.capacity() < k)
|
||||
{
|
||||
pixelBuffer = BufferUtils.createIntBuffer(k);
|
||||
pixelValues = new int[k];
|
||||
}
|
||||
|
||||
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
pixelBuffer.clear();
|
||||
|
||||
if (OpenGlHelper.isFramebufferEnabled()) {
|
||||
GlStateManager.bindTexture(buffer.framebufferTexture);
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
|
||||
}
|
||||
else {
|
||||
GL11.glReadPixels(0, 0, width, height, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
|
||||
}
|
||||
|
||||
pixelBuffer.get(pixelValues);
|
||||
TextureUtil.processPixelValues(pixelValues, width, height);
|
||||
BufferedImage bufferedimage = null;
|
||||
|
||||
|
||||
if (OpenGlHelper.isFramebufferEnabled())
|
||||
{
|
||||
bufferedimage = new BufferedImage(buffer.framebufferWidth, buffer.framebufferHeight, 1);
|
||||
int l = buffer.framebufferTextureHeight - buffer.framebufferHeight;
|
||||
|
||||
for (int i1 = l; i1 < buffer.framebufferTextureHeight; ++i1)
|
||||
{
|
||||
for (int j1 = 0; j1 < buffer.framebufferWidth; ++j1)
|
||||
{
|
||||
bufferedimage.setRGB(j1, i1 - l, pixelValues[i1 * buffer.framebufferTextureWidth + j1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
bufferedimage = new BufferedImage(width, height, 1);
|
||||
bufferedimage.setRGB(0, 0, width, height, pixelValues, 0, width);
|
||||
}
|
||||
|
||||
|
||||
return bufferedimage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.monte.media.Buffer;
|
||||
import org.monte.media.Format;
|
||||
import org.monte.media.FormatKeys;
|
||||
import org.monte.media.FormatKeys.MediaType;
|
||||
import org.monte.media.MovieWriter;
|
||||
import org.monte.media.Registry;
|
||||
import org.monte.media.VideoFormatKeys;
|
||||
import org.monte.media.math.Rational;
|
||||
|
||||
import eu.crushedpixel.replaymod.ReplayMod;
|
||||
|
||||
public class VideoWriter {
|
||||
|
||||
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
|
||||
private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
|
||||
|
||||
private static final String VIDEO_EXTENSION = ".avi";
|
||||
|
||||
private static MovieWriter out;
|
||||
private static boolean isRecording = false;
|
||||
private static boolean requestFinish = false;
|
||||
|
||||
private static Buffer buf;
|
||||
private static int track;
|
||||
|
||||
public static boolean isRecording() {
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
public static void startRecording(int width, int height) {
|
||||
if(isRecording) {
|
||||
IllegalStateException up = new IllegalStateException("VideoWriter is already recording!");
|
||||
throw up; //lolololo
|
||||
}
|
||||
isRecording = true;
|
||||
|
||||
try {
|
||||
File folder = new File("./replay_videos/");
|
||||
folder.mkdirs();
|
||||
|
||||
String fileName = sdf.format(Calendar.getInstance().getTime());
|
||||
|
||||
File file = new File(folder, fileName+VIDEO_EXTENSION);
|
||||
file.createNewFile();
|
||||
|
||||
out = Registry.getInstance().getWriter(file);
|
||||
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
|
||||
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
|
||||
FormatKeys.FrameRateKey, new Rational(ReplayMod.replaySettings.getVideoFramerate(), 1),
|
||||
VideoFormatKeys.WidthKey, width,
|
||||
VideoFormatKeys.HeightKey, height,
|
||||
VideoFormatKeys.DepthKey, 24,
|
||||
VideoFormatKeys.QualityKey, ReplayMod.replaySettings.getVideoQuality());
|
||||
|
||||
|
||||
track = out.addTrack(format);
|
||||
|
||||
buf = new Buffer();
|
||||
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
|
||||
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeImage(BufferedImage image) {
|
||||
if(requestFinish || !isRecording) {
|
||||
IllegalStateException up = new IllegalStateException(
|
||||
"The VideoWriter is currently not available. Please try again later.");
|
||||
throw up; //lolololo^2
|
||||
}
|
||||
|
||||
try {
|
||||
buf.data = image;
|
||||
out.write(track, buf);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void endRecording() {
|
||||
if(!isRecording) return;
|
||||
isRecording = false;
|
||||
try {
|
||||
out.close();
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user