commit e6bfc8abd562f6dc1489fcabc0f1e8334235eb52 Author: lemon-sh Date: Fri Aug 20 10:04:45 2021 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c001800 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea +.gradle +**/build/ +!src/**/build/ +gradle-app.setting +!gradle-wrapper.jar +.gradletasknamecache +out/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..82eb029 --- /dev/null +++ b/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '7.0.0' +} + +group 'moe.lemonsh' +version '0.1' + +jar { + manifest { + attributes 'Main-Class': 'moe.lemonsh.waifu2xlemon.App' + } +} + + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.slf4j:slf4j-simple:1.7.32' + implementation 'com.sparkjava:spark-core:2.9.3' +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..69a9715 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..744e882 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..8c164aa --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'waifu2xLemon' + diff --git a/src/main/java/moe/lemonsh/waifu2xlemon/App.java b/src/main/java/moe/lemonsh/waifu2xlemon/App.java new file mode 100644 index 0000000..26621d0 --- /dev/null +++ b/src/main/java/moe/lemonsh/waifu2xlemon/App.java @@ -0,0 +1,90 @@ +package moe.lemonsh.waifu2xlemon; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.http.Part; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.stream.Collectors; + +import static spark.Spark.*; + +public class App { + private static final int maxFileSize = 8380416; + public static void main(String[] argv) throws IOException { + String envport = System.getenv("WAIFU2XLEMON_PORT"); + String envexec = System.getenv("WAIFU2XLEMON_EXECUTABLE"); + if (envport == null || envexec == null) { + System.out.println("Error: Invalid config. Make sure that the WAIFU2XLEMON_PORT and WAIFU2XLEMON_EXECUTABLE envvars are set."); + System.exit(1); + } + Waifu2x waifu2x = new Waifu2x(envexec); + threadPool(4, 1, -1); + try { + port(Integer.parseInt(envport)); + } catch (NumberFormatException e) { + System.out.println("Error: Invalid config. WAIFU2XLEMON_PORT contains an invalid value."); + System.exit(2); + } + MultipartConfigElement multipartConfigElement = new MultipartConfigElement("multipartTemp", maxFileSize, maxFileSize, maxFileSize+1); + String indexPage; + try (InputStream indexResource = App.class.getResourceAsStream("/app.html")) { + indexPage = new BufferedReader(new InputStreamReader(Objects.requireNonNull(indexResource))).lines().collect(Collectors.joining("\n")); + } + get("/", (req, resp) -> indexPage); + post("/", (req, resp) -> { + req.raw().setAttribute("org.eclipse.jetty.multipartConfig", multipartConfigElement); + Part filePart, noisePart, typePart, scalePart; + try { + filePart = req.raw().getPart("file"); + scalePart = req.raw().getPart("scale"); + noisePart = req.raw().getPart("noise"); + typePart = req.raw().getPart("ftype"); + } catch (IllegalStateException e) { + return null; + } + if (filePart == null || scalePart == null || noisePart == null || typePart == null) halt(400); + int scale = 0, noise = 0, type = 0; + try { + scale = Integer.parseInt(new String(scalePart.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); + noise = Integer.parseInt(new String(noisePart.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); + type = Integer.parseInt(new String(typePart.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); + } catch (NumberFormatException e) { + halt(400); + } + if (scale < 1 || scale > 2 || noise < -1 || noise > 2 || type < 0 || type > 3) halt(400); // boundary check + String mimeType = URLConnection.guessContentTypeFromName(filePart.getSubmittedFileName()); + String inputExtension, outputExtension = null; + switch (mimeType) { + case "image/png" -> inputExtension = ".png"; + case "image/jpeg" -> inputExtension = ".jpg"; + case "image/webp" -> inputExtension = ".webp"; + default -> { + return "Only PNG/JPG/WEBP files are allowed."; + } + } + switch (type) { + case 0 -> outputExtension = ".png"; + case 1 -> outputExtension = ".jpg"; + case 2 -> outputExtension = ".webp"; + default -> halt(400); + } + Waifu2x.WaifuResult waifuResult; + waifuResult = waifu2x.run(filePart.getInputStream(), inputExtension, outputExtension, (char)(noise+'0'), (char)(scale+'0')); + if (waifuResult.success) { + resp.raw().setContentType(URLConnection.guessContentTypeFromName(waifuResult.filename)); + resp.raw().setHeader("Content-Disposition","attachment; filename="+waifuResult.filename); + resp.raw().setContentLengthLong(waifuResult.size); + waifuResult.image.transferTo(resp.raw().getOutputStream()); + resp.raw().getOutputStream().flush(); + resp.raw().getOutputStream().close(); + return null; + } + return "Error.\n"+waifuResult.stdout; + }); + } +} diff --git a/src/main/java/moe/lemonsh/waifu2xlemon/Waifu2x.java b/src/main/java/moe/lemonsh/waifu2xlemon/Waifu2x.java new file mode 100644 index 0000000..2e231b4 --- /dev/null +++ b/src/main/java/moe/lemonsh/waifu2xlemon/Waifu2x.java @@ -0,0 +1,65 @@ +package moe.lemonsh.waifu2xlemon; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +public class Waifu2x { + public static class WaifuResult { + public boolean success; + public String stdout; + public InputStream image; + public String filename; + public long size; + } + + private final Object GPULock; + private final Path ExecutablePath; + private final Logger log = LoggerFactory.getLogger(Waifu2x.class); + + public Waifu2x(String wf2xExecutablePath) { + var executable = new File(wf2xExecutablePath); + if (!executable.canExecute()) { + throw new IllegalArgumentException("'%s' is not a valid path.".formatted(wf2xExecutablePath)); + } else { + ExecutablePath = executable.toPath().toAbsolutePath(); + GPULock = new Object(); + } + } + + public WaifuResult run(InputStream inputImage, String inputExtension, String outputExtension, char noise, char scale) throws IOException, InterruptedException { + var inputFile = new File("wfx_input" + inputExtension).toPath().toAbsolutePath(); + var outputFile = new File("wfx_output" + outputExtension); + var outputFilePath = outputFile.toPath().toAbsolutePath(); + Files.deleteIfExists(inputFile); + Files.copy(inputImage, inputFile); + Process waifuProcess; + synchronized (GPULock) { + waifuProcess = new ProcessBuilder(ExecutablePath.toString(), "-s", Character.toString(scale), "-n", + Character.toString(noise), "-i", inputFile.toString(), "-o", outputFilePath.toString()).start(); + waifuProcess.waitFor(); + } + var result = new WaifuResult(); + result.filename = outputFile.getName(); + Files.delete(inputFile); + String stdout = new String(waifuProcess.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); + log.info(stdout); + result.stdout = stdout; + if (!(result.success = outputFile.isFile())) return result; + var outputBytes = new ByteArrayOutputStream() { + @Override + public synchronized byte[] toByteArray() { + return buf; + } + }; + Files.copy(outputFilePath, outputBytes); + Files.delete(outputFilePath); + result.image = new ByteArrayInputStream(outputBytes.toByteArray(), 0, outputBytes.size()); + result.size = outputBytes.size(); + return result; + } +} diff --git a/src/main/resources/app.html b/src/main/resources/app.html new file mode 100644 index 0000000..b0ae190 --- /dev/null +++ b/src/main/resources/app.html @@ -0,0 +1,56 @@ + + + + + + + Waifu2xLemon + + + +
+

Waifu2xLemon

+

Simple web interface for waifu2x-ncnn-vulkan

+
+ +
Scale + + + + +
Noise reduction + + + + + + + + +
Output type + + + + + + +
+
+ by ./lemon.sh +
+ +