Add direct upload to YouTube support

This commit is contained in:
johni0702
2015-11-07 11:47:20 +01:00
parent 335d5ba790
commit e2bf7503b6
11 changed files with 705 additions and 13 deletions

View File

@@ -49,16 +49,25 @@ repositories {
}
}
configurations {
shade
compile.extendsFrom shade
}
dependencies {
compile 'org.projectlombok:lombok:1.16.4'
compile 'org.spongepowered:mixin:0.4.3'
compile 'com.googlecode.mp4parser:isoparser:1.1.7'
compile 'org.apache.commons:commons-exec:1.3'
shade 'com.googlecode.mp4parser:isoparser:1.1.7'
shade 'org.apache.commons:commons-exec:1.3'
shade 'com.google.apis:google-api-services-youtube:v3-rev178-1.22.0'
shade 'com.google.api-client:google-api-client-gson:1.20.0'
shade 'com.google.api-client:google-api-client-java6:1.20.0'
shade 'com.google.oauth-client:google-oauth-client-jetty:1.20.0'
compile 'org.aspectj:aspectjrt:1.8.2'
shade 'org.aspectj:aspectjrt:1.8.2'
compile project(':jGui')
shade project(':jGui')
compile project(':ReplayStudio')
testCompile 'junit:junit:4.11'
@@ -68,11 +77,14 @@ jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
dependsOn configurations.compile
dependsOn configurations.shade
dependsOn ':ReplayStudio:shadowJar'
def shade = {files(configurations.compile.findAll { c ->
['mixin', 'isoparser', 'aspectjrt', 'jGui', 'commons-exec'].any {c.name.startsWith("$it-")}
} + getTasks().getByPath(':ReplayStudio:shadowJar').outputs.files)}
def shade = {files(
configurations.compile.findAll {it.name.startsWith 'mixin-'}
+ configurations.shade
+ getTasks().getByPath(':ReplayStudio:shadowJar').outputs.files
)}
def noticeDir = file("$buildDir/NOTICE")
doFirst {

View File

@@ -3,6 +3,7 @@ package com.replaymod.extras;
import com.replaymod.core.ReplayMod;
import com.replaymod.extras.playeroverview.PlayerOverview;
import com.replaymod.extras.urischeme.UriSchemeExtra;
import com.replaymod.extras.youtube.YoutubeUpload;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
@@ -24,6 +25,7 @@ public class ReplayModExtras {
private static final List<Class<? extends Extra>> builtin = Arrays.asList(
PlayerOverview.class,
UriSchemeExtra.class,
YoutubeUpload.class,
FullBrightness.class,
HotkeyButtons.class,
LocalizationExtra.class,

View File

@@ -0,0 +1,247 @@
package com.replaymod.extras.youtube;
import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoSnippet;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.replaymod.core.utils.Utils;
import com.replaymod.render.RenderSettings;
import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.RenderInfo;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.*;
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
import de.johni0702.minecraft.gui.element.advanced.GuiTextArea;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup;
import joptsimple.internal.Strings;
import lombok.RequiredArgsConstructor;
import net.minecraft.client.resources.I18n;
import org.apache.commons.io.IOUtils;
import org.lwjgl.Sys;
import org.lwjgl.util.ReadableDimension;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import static java.util.Arrays.asList;
@RequiredArgsConstructor
public class GuiYoutubeUpload extends GuiScreen {
private final GuiScreen previousScreen;
private final File videoFile;
private final int videoFrames;
private final RenderSettings settings;
private String thumbnailFormat;
private byte[] thumbnailImage;
public final Runnable inputValidation = new Runnable() {
@Override
public void run() {
String problem = null;
if (nameField.getText().isEmpty()) problem = "replaymod.gui.titleempty";
if (thumbnailImage != null) {
if (thumbnailImage.length > 1024 * 1024 * 2) problem = "replaymod.gui.videothumbnailtoolarge";
if (!asList("jpeg", "png").contains(thumbnailFormat)) problem = "replaymod.gui.videothumbnailformat";
}
if (upload == null) {
if (problem == null) {
uploadButton.setEnabled();
uploadButton.setTooltip(null);
} else {
uploadButton.setDisabled();
uploadButton.setTooltip(new GuiTooltip().setI18nText(problem));
}
}
}
};
public final GuiTextField nameField = new GuiTextField().setI18nHint("replaymod.gui.videotitle")
.onTextChanged(s -> inputValidation.run());
public final GuiTextArea descriptionField = new GuiTextArea().setMaxCharCount(Integer.MAX_VALUE)
.setMaxTextWidth(Integer.MAX_VALUE).setMaxTextHeight(Integer.MAX_VALUE);
{
descriptionField.setText(new String[]{I18n.format("replaymod.gui.videodescription")});
}
public final GuiTextField tagsField = new GuiTextField().setI18nHint("replaymod.gui.videotags");
{
nameField.setNext(descriptionField)
.getNext().setNext(tagsField)
.getNext().setNext(nameField);
}
public final GuiProgressBar progressBar = new GuiProgressBar();
public final GuiPanel leftPanel = new GuiPanel(this).setLayout(new CustomLayout<GuiPanel>() {
@Override
protected void layout(GuiPanel container, int width, int height) {
size(nameField, width, 20);
size(descriptionField, width, height - 90);
size(tagsField, width, 20);
size(progressBar, width, 20);
pos(nameField, 0, 0);
pos(descriptionField, 0, 30);
pos(tagsField, 0, height - 50);
pos(progressBar, 0, height - 20);
}
}).addElements(null, nameField, descriptionField, tagsField, progressBar);
public final GuiDropdownMenu<VideoVisibility> visibilityDropdown = new GuiDropdownMenu<VideoVisibility>()
.setSize(200, 20).setValues(VideoVisibility.values()).setSelected(VideoVisibility.PUBLIC);
public final GuiButton thumbnailButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
Futures.addCallback(
GuiFileChooserPopup.openLoadGui(GuiYoutubeUpload.this, "replaymod.gui.load",
ImageIO.getReaderFileSuffixes()).getFuture(),
new FutureCallback<File>() {
@Override
public void onSuccess(@Nullable File result) {
if (result != null) {
thumbnailButton.setLabel(result.getName());
BufferedImage image;
try {
thumbnailImage = IOUtils.toByteArray(new FileInputStream(result));
ImageInputStream in = ImageIO.createImageInputStream(new ByteArrayInputStream(thumbnailImage));
ImageReader reader = ImageIO.getImageReaders(in).next();
thumbnailFormat = reader.getFormatName().toLowerCase();
image = ImageIO.read(new ByteArrayInputStream(thumbnailImage));
} catch (Throwable t) {
t.printStackTrace();
thumbnailImage = null;
image = null;
}
thumbnail.setTexture(image);
inputValidation.run();
}
}
@Override
public void onFailure(Throwable t) {
throw new RuntimeException(t);
}
});
}
}).setSize(200, 20).setI18nLabel("replaymod.gui.videothumbnail");
public final GuiImage thumbnail = new GuiImage().setSize(200, 112).setTexture(Utils.DEFAULT_THUMBNAIL);
public final GuiButton uploadButton = new GuiButton(this).setSize(98, 20);
public final GuiButton closeButton = new GuiButton(this).onClick(new Runnable() {
@Override
public void run() {
previousScreen.display();
}
}).setSize(98, 20).setI18nLabel("replaymod.gui.back");
public final GuiPanel rightPanel = new GuiPanel(this)
.addElements(null, visibilityDropdown, thumbnailButton, thumbnail)
.setLayout(new VerticalLayout(VerticalLayout.Alignment.TOP).setSpacing(10));
private YoutubeUploader upload;
{
setLayout(new CustomLayout<GuiScreen>() {
@Override
protected void layout(GuiScreen container, int width, int height) {
pos(leftPanel, 10, 10);
size(leftPanel, width - 200 - 30, height - 20);
pos(rightPanel, width - 210, 10);
pos(uploadButton, width - 210, height - 30);
pos(closeButton, width - 108, height - 30);
}
});
setState(false);
inputValidation.run();
}
private void setState(boolean uploading) {
forEach(GuiElement.class).setEnabled(!uploading);
uploadButton.setEnabled();
if (uploading) {
uploadButton.onClick(() -> {
try {
upload.cancel();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).setI18nLabel("replaymod.gui.cancel");
} else {
uploadButton.onClick(() -> {
try {
setState(true);
VideoVisibility visibility = visibilityDropdown.getSelectedValue();
VideoSnippet snippet = new VideoSnippet();
snippet.setTitle(nameField.getText());
snippet.setDescription(Strings.join(descriptionField.getText(), "\n"));
snippet.setTags(asList(tagsField.getText().split(",")));
upload = new YoutubeUploader(getMinecraft(), videoFile, videoFrames,
thumbnailFormat, thumbnailImage, settings, visibility, snippet);
ListenableFuture<Video> future = upload.upload();
Futures.addCallback(future, new FutureCallback<Video>() {
@Override
public void onSuccess(Video result) {
String url = "https://youtu.be/" + result.getId();
try {
Desktop.getDesktop().browse(new URL(url).toURI());
} catch(Throwable throwable) {
Sys.openURL(url);
}
upload = null;
progressBar.setLabel(I18n.format("replaymod.gui.ytuploadprogress.done", url));
setState(false);
}
@Override
public void onFailure(Throwable t) {
if (t instanceof InterruptedException) {
progressBar.setLabel("0%");
} else {
t.printStackTrace();
progressBar.setLabel(t.getLocalizedMessage());
}
upload = null;
setState(false);
}
});
} catch (GeneralSecurityException | IOException e) {
e.printStackTrace();
}
}).setI18nLabel("replaymod.gui.upload");
}
}
@Override
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
if (upload != null && upload.getState() != null) {
progressBar.setProgress((float) upload.getProgress());
progressBar.setI18nLabel("replaymod.gui.ytuploadprogress." + upload.getState().name().toLowerCase());
}
super.draw(renderer, size, renderInfo);
}
}

View File

@@ -0,0 +1,12 @@
package com.replaymod.extras.youtube;
import net.minecraft.client.resources.I18n;
public enum VideoVisibility {
PUBLIC, UNLISTED, PRIVATE;
@Override
public String toString() {
return I18n.format("replaymod.gui.videovisibility." + name().toLowerCase());
}
}

View File

@@ -0,0 +1,35 @@
package com.replaymod.extras.youtube;
import com.replaymod.core.ReplayMod;
import com.replaymod.extras.Extra;
import com.replaymod.render.gui.GuiRenderingDone;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
public class YoutubeUpload implements Extra {
@Override
public void register(ReplayMod mod) throws Exception {
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void onGuiOpen(GuiScreenEvent.InitGuiEvent.Post event) {
if (GuiScreen.from(event.gui) instanceof GuiRenderingDone) {
GuiRenderingDone gui = (GuiRenderingDone) GuiScreen.from(event.gui);
// Check if there already is a youtube button
if (gui.actionsPanel.getChildren().stream().anyMatch(it -> it instanceof YoutubeButton)) {
return; // Button already added
}
// Add the Upload to YouTube button to actions panel
gui.actionsPanel.addElements(null,
new YoutubeButton().onClick(() ->
new GuiYoutubeUpload(gui, gui.videoFile, gui.videoFrames, gui.settings).display()
).setSize(200, 20).setI18nLabel("replaymod.gui.youtubeupload"));
}
}
private static class YoutubeButton extends GuiButton {}
}

View File

@@ -0,0 +1,237 @@
package com.replaymod.extras.youtube;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.store.DataStoreFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.YouTubeScopes;
import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoSnippet;
import com.google.api.services.youtube.model.VideoStatus;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.replaymod.render.RenderSettings;
import com.replaymod.render.metadata.MetadataInjector;
import lombok.Getter;
import lombok.NonNull;
import net.minecraft.client.Minecraft;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.io.FileUtils;
import java.io.*;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
public class YoutubeUploader {
private static final String CLIENT_ID = "743126594724-mfe7pj1k7e47uu5pk4503c8st9vj9ibu.apps.googleusercontent.com";
private static final String CLIENT_SECRET = "gMwcy3mRYCRamCIjJIYP7rqc";
private static final String FFMPEG_ODS =
"-i %s -vf scale=iw:iw*9/16,setdar=16:9 -c:v libx264 -preset slow -crf 16 %s";
private static final JsonFactory JSON_FACTORY = new GsonFactory();
private final NetHttpTransport httpTransport;
private final DataStoreFactory dataStoreFactory;
private final File videoFile;
private final int videoFrames;
private final String thumbnailFormat;
private final byte[] thumbnailImage;
private final RenderSettings settings;
private final VideoSnippet videoSnippet;
private final VideoVisibility videoVisibility;
private Thread thread;
@NonNull
private Supplier<Double> progress = Suppliers.ofInstance(0d);
@Getter
private State state;
public YoutubeUploader(Minecraft minecraft, File videoFile, int videoFrames,
String thumbnailFormat, byte[] thumbnailImage,
RenderSettings settings, VideoVisibility videoVisibility, VideoSnippet videoSnippet)
throws GeneralSecurityException, IOException {
this.videoFile = videoFile;
this.videoFrames = videoFrames;
this.thumbnailImage = thumbnailImage;
this.thumbnailFormat = thumbnailFormat;
this.settings = settings;
this.videoVisibility = videoVisibility;
this.videoSnippet = videoSnippet;
this.httpTransport = GoogleNetHttpTransport.newTrustedTransport();
this.dataStoreFactory = new FileDataStoreFactory(minecraft.mcDataDir);
}
public ListenableFuture<Video> upload() throws IOException {
final SettableFuture<Video> future = SettableFuture.create();
thread = new Thread(() -> {
try {
state = State.AUTH;
Credential credential = auth();
state = State.PREPARE_VIDEO;
File processedFile = preUpload();
progress = Suppliers.ofInstance(0d);
YouTube youTube = new YouTube.Builder(httpTransport, JSON_FACTORY, credential)
.setApplicationName("ReplayMod").build();
state = State.UPLOAD;
Video video = doUpload(youTube, processedFile);
if (thumbnailImage != null) {
doThumbUpload(youTube, video);
}
state = State.CLEANUP;
postUpload(processedFile);
future.set(video);
} catch (Throwable t) {
future.setException(t);
}
});
thread.start();
return future;
}
public void cancel() throws InterruptedException {
thread.interrupt();
thread.join();
}
private Credential auth() throws IOException {
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, CLIENT_ID, CLIENT_SECRET,
Collections.singleton(YouTubeScopes.YOUTUBE_UPLOAD)
).setDataStoreFactory(dataStoreFactory).build();
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}
private File preUpload() throws InterruptedException, IOException {
if (settings.getRenderMethod() == RenderSettings.RenderMethod.ODS) {
File tmpFile = new File(videoFile.getParentFile(), System.currentTimeMillis() + ".mp4");
tmpFile.deleteOnExit();
String args = String.format(FFMPEG_ODS, videoFile.getName(), tmpFile.getName());
CommandLine commandLine = new CommandLine(settings.getExportCommand());
commandLine.addArguments(args);
System.out.println("Re-encoding for ODS with " + settings.getExportCommand() + args);
Process process = new ProcessBuilder(commandLine.toStrings()).directory(videoFile.getParentFile()).start();
final AtomicBoolean active = new AtomicBoolean(true);
final InputStream in = process.getErrorStream();
new Thread(() -> {
try {
StringBuilder sb = new StringBuilder();
while (active.get()) {
char c = (char) in.read();
if (c == '\r') {
String str = sb.toString();
System.out.println(str);
if (str.startsWith("frame=")) {
str = str.substring(6).trim();
str = str.substring(0, str.indexOf(' '));
double frame = Integer.parseInt(str);
progress = Suppliers.ofInstance(frame / videoFrames);
}
sb = new StringBuilder();
} else {
sb.append(c);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
int result;
try {
result = process.waitFor();
} catch (InterruptedException e) {
process.destroy();
throw e;
} finally {
active.set(false);
}
if (result != 0) {
throw new IOException("FFmpeg returned: " + result);
}
MetadataInjector.injectODSMetadata(tmpFile);
return tmpFile;
}
return videoFile;
}
private Video doUpload(YouTube youTube, File processedFile) throws IOException {
Video video = new Video();
VideoStatus videoStatus = new VideoStatus();
videoStatus.setPrivacyStatus(videoVisibility.name().toLowerCase());
video.setStatus(videoStatus);
video.setSnippet(videoSnippet);
Video result;
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(processedFile))) {
InputStreamContent content = new InputStreamContent("video/*", inputStream);
content.setLength(processedFile.length());
YouTube.Videos.Insert videoInsert = youTube.videos().insert("snippet,statistics,status", video, content);
final MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();
progress = () -> {
try {
return uploader.getProgress();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
result = videoInsert.execute();
}
return result;
}
private void doThumbUpload(YouTube youTube, Video video) throws IOException {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(thumbnailImage)) {
InputStreamContent content = new InputStreamContent("image/" + thumbnailFormat, inputStream);
content.setLength(inputStream.available());
youTube.thumbnails().set(video.getId(), content).execute();
} catch (GoogleJsonResponseException e) {
GoogleJsonError.ErrorInfo info = e.getDetails().getErrors().get(0);
if ("Authorization".equals(info.getLocation()) && "forbidden".equals(info.getReason())) {
e.printStackTrace();
// TODO: When rewriting, show popup gui
} else {
throw e;
}
}
}
private void postUpload(File processedFile) {
if (processedFile != videoFile) {
FileUtils.deleteQuietly(processedFile);
}
}
public double getProgress() {
return progress.get();
}
public enum State {
AUTH, PREPARE_VIDEO, UPLOAD, CLEANUP
}
}

View File

@@ -5,4 +5,6 @@ import com.replaymod.core.SettingsRegistry;
public final class Setting<T> {
public static final SettingsRegistry.SettingKey<String> RENDER_PATH =
new SettingsRegistry.SettingKeys<>("advanced", "renderPath", null, "./replay_videos/");
public static final SettingsRegistry.SettingKey<Boolean> SKIP_POST_RENDER_GUI =
new SettingsRegistry.SettingKeys<>("advanced", "skipPostRenderGui", null, false);
}

View File

@@ -9,12 +9,10 @@ import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.lwjgl.util.ReadableDimension;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.*;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.TimeUnit;
@@ -30,6 +28,8 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
private final String commandArgs;
private volatile boolean aborted;
private ByteArrayOutputStream ffmpegLog = new ByteArrayOutputStream(4096);
public VideoWriter(final RenderSettings settings) throws IOException {
this.settings = settings;
@@ -47,7 +47,7 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
System.out.println("Starting " + settings.getExportCommand() + " with args: " + commandArgs);
String[] cmdline = new CommandLine(executable).addArguments(commandArgs).toStrings();
process = new ProcessBuilder(cmdline).directory(outputFolder).start();
OutputStream exportLogOut = new FileOutputStream("export.log");
OutputStream exportLogOut = new TeeOutputStream(new FileOutputStream("export.log"), ffmpegLog);
new StreamPipe(process.getInputStream(), exportLogOut).start();
new StreamPipe(process.getErrorStream(), exportLogOut).start();
outputStream = process.getOutputStream();
@@ -110,4 +110,15 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
public void abort() {
aborted = true;
}
public File getVideoFile() {
String log = ffmpegLog.toString();
for (String line : log.split("\n")) {
if (line.startsWith("Output #0")) {
String fileName = line.substring(line.indexOf(", to '") + 6, line.lastIndexOf('\''));
return new File(settings.getOutputFile().getParentFile(), fileName);
}
}
throw new IllegalStateException("No output file found.");
}
}

View File

@@ -0,0 +1,109 @@
package com.replaymod.render.gui;
import com.replaymod.core.SettingsRegistry;
import com.replaymod.render.RenderSettings;
import com.replaymod.render.ReplayModRender;
import com.replaymod.render.Setting;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiCheckbox;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import lombok.RequiredArgsConstructor;
import net.minecraft.util.Util;
import org.apache.logging.log4j.LogManager;
import org.lwjgl.Sys;
import java.awt.*;
import java.io.File;
import java.io.IOException;
@RequiredArgsConstructor
public class GuiRenderingDone extends GuiScreen {
public final ReplayModRender mod;
public final File videoFile;
public final int videoFrames;
public final RenderSettings settings;
public final GuiLabel infoLine1 = new GuiLabel().setI18nText("replaymod.gui.renderdone1");
public final GuiLabel infoLine2 = new GuiLabel().setI18nText("replaymod.gui.renderdone2");
public final GuiButton openFolder = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
File folder = videoFile.getParentFile();
String path = folder.getAbsolutePath();
// First try OS specific methods
try {
switch (Util.getOSType()) {
case WINDOWS:
Runtime.getRuntime().exec(String.format("cmd.exe /C start \"Open file\" \"%s\"", path));
return;
case OSX:
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", path});
return;
}
} catch (IOException e) {
LogManager.getLogger().error("Cannot open file", e);
}
// Otherwise try the java way
try {
Desktop.getDesktop().browse(folder.toURI());
} catch (Throwable throwable) {
// And if all fails, lwjgl
Sys.openURL("file://" + path);
}
}
}).setSize(200, 20).setI18nLabel("replaymod.gui.openfolder");
public final GuiPanel actionsPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10))
.addElements(null, openFolder);
public final GuiPanel mainPanel = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(10))
.addElements(new VerticalLayout.Data(0.5),
new GuiPanel().setLayout(new VerticalLayout().setSpacing(4)).addElements(null, infoLine1, infoLine2),
actionsPanel);
public final GuiButton closeButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
if (neverOpenCheckbox.isChecked()) {
SettingsRegistry settingsRegistry = mod.getCore().getSettingsRegistry();
settingsRegistry.set(Setting.SKIP_POST_RENDER_GUI, true);
settingsRegistry.save();
}
getMinecraft().displayGuiScreen(null);
}
}).setSize(100, 20).setI18nLabel("replaymod.gui.close");
public final GuiCheckbox neverOpenCheckbox = new GuiCheckbox().setI18nLabel("replaymod.gui.notagain");
public final GuiPanel closePanel = new GuiPanel(this)
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(5))
.addElements(new HorizontalLayout.Data(0.5), neverOpenCheckbox, closeButton);
{
setLayout(new CustomLayout<GuiScreen>() {
@Override
protected void layout(GuiScreen container, int width, int height) {
pos(mainPanel, width / 2 - width(mainPanel) / 2, height / 3 - height(mainPanel) / 2);
pos(closePanel, width - 10 - width(closePanel), height - 10 - height(closePanel));
}
});
setTitle(new GuiLabel().setI18nText("replaymod.gui.renderdonetitle"));
setBackground(Background.DIRT);
}
@Override
public void display() {
if (mod.getCore().getSettingsRegistry().get(Setting.SKIP_POST_RENDER_GUI)) {
return;
}
super.display();
}
}

View File

@@ -4,9 +4,11 @@ import com.replaymod.core.ReplayMod;
import com.replaymod.pathing.player.AbstractTimelinePlayer;
import com.replaymod.pathing.properties.TimestampProperty;
import com.replaymod.render.RenderSettings;
import com.replaymod.render.ReplayModRender;
import com.replaymod.render.VideoWriter;
import com.replaymod.render.capturer.RenderInfo;
import com.replaymod.render.frame.RGBFrame;
import com.replaymod.render.gui.GuiRenderingDone;
import com.replaymod.render.gui.GuiVideoRenderer;
import com.replaymod.render.hooks.ChunkLoadingRenderGlobal;
import com.replaymod.render.metadata.MetadataInjector;
@@ -216,7 +218,7 @@ public class VideoRenderer implements RenderInfo {
ReplayMod.soundHandler.playRenderSuccessSound();
mc.displayGuiScreen(null);
new GuiRenderingDone(ReplayModRender.instance, videoWriter.getVideoFile(), totalFrames, settings).display();
}
private void tick() {

View File

@@ -89,6 +89,29 @@ replaymod.gui.iphidden=Server IP Hidden
replaymod.gui.overwrite=Overwrite
replaymod.gui.saveas=Save as ...
replaymod.gui.done=Done
replaymod.gui.close=Close
replaymod.gui.notagain=Don't show again
replaymod.gui.renderdonetitle=Video rendered
replaymod.gui.openfolder=Open Video Folder
replaymod.gui.youtubeupload=Upload to YouTube
replaymod.gui.renderdone1=Your video was successfully rendered.
replaymod.gui.renderdone2=How would you like to proceed?
replaymod.gui.videotitle=Title
replaymod.gui.videodescription=Description
replaymod.gui.videotags=Tags,Tags,Tags
replaymod.gui.videothumbnail=Video Thumbnail
replaymod.gui.videovisibility.private=Private
replaymod.gui.videovisibility.unlisted=Unlisted
replaymod.gui.videovisibility.public=Public
replaymod.gui.ytuploadprogress.auth=[1/4] Authorization
replaymod.gui.ytuploadprogress.prepare_video=[2/4] Preparing video: %%d%%%%
replaymod.gui.ytuploadprogress.upload=[3/4] Uploading: %%d%%%%
replaymod.gui.ytuploadprogress.cleanup=[4/4] Cleanup
replaymod.gui.ytuploadprogress.done=Done: %s
replaymod.gui.titleempty=Title cannot be empty
replaymod.gui.videothumbnailtoolarge=Thumbnail size exceeds 2MB
replaymod.gui.videothumbnailformat=Thumbnail has to be either JPEG or PNG format
replaymod.gui.original=Original
replaymod.gui.modified=Modified