From e2bf7503b6ccf1f2a0dbf789d35b448ed643aca8 Mon Sep 17 00:00:00 2001 From: johni0702 Date: Sat, 7 Nov 2015 11:47:20 +0100 Subject: [PATCH] Add direct upload to YouTube support --- build.gradle | 26 +- .../com/replaymod/extras/ReplayModExtras.java | 2 + .../extras/youtube/GuiYoutubeUpload.java | 247 ++++++++++++++++++ .../extras/youtube/VideoVisibility.java | 12 + .../extras/youtube/YoutubeUpload.java | 35 +++ .../extras/youtube/YoutubeUploader.java | 237 +++++++++++++++++ .../java/com/replaymod/render/Setting.java | 2 + .../com/replaymod/render/VideoWriter.java | 21 +- .../render/gui/GuiRenderingDone.java | 109 ++++++++ .../render/rendering/VideoRenderer.java | 4 +- .../assets/replaymod/lang/en_US.lang | 23 ++ 11 files changed, 705 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java create mode 100644 src/main/java/com/replaymod/extras/youtube/VideoVisibility.java create mode 100644 src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java create mode 100644 src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java create mode 100644 src/main/java/com/replaymod/render/gui/GuiRenderingDone.java diff --git a/build.gradle b/build.gradle index 791374aa..d561e12f 100755 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/src/main/java/com/replaymod/extras/ReplayModExtras.java b/src/main/java/com/replaymod/extras/ReplayModExtras.java index 13ceb481..b56c26ac 100644 --- a/src/main/java/com/replaymod/extras/ReplayModExtras.java +++ b/src/main/java/com/replaymod/extras/ReplayModExtras.java @@ -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> builtin = Arrays.asList( PlayerOverview.class, UriSchemeExtra.class, + YoutubeUpload.class, FullBrightness.class, HotkeyButtons.class, LocalizationExtra.class, diff --git a/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java new file mode 100644 index 00000000..6dad37aa --- /dev/null +++ b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java @@ -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() { + @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 visibilityDropdown = new GuiDropdownMenu() + .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() { + @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() { + @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