Created
July 23, 2020 10:53
-
-
Save rock3r/4ae3931cda60291e01a76874d418fdfc to your computer and use it in GitHub Desktop.
Kscript program to convert a sequence of images to a webp animation, using img2webp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
//usr/bin/env echo ' | |
/**** BOOTSTRAP kscript ****\'>/dev/null | |
command -v kscript >/dev/null 2>&1 || curl -L "https://git.io/fpF1K" | bash 1>&2 | |
exec kscript $0 "$@" | |
\*** IMPORTANT: Any code including imports and annotations must come after this line ***/ | |
@file:DependsOn("com.github.ajalt:clikt:2.8.0") | |
import com.github.ajalt.clikt.core.CliktCommand | |
import com.github.ajalt.clikt.parameters.arguments.argument | |
import com.github.ajalt.clikt.parameters.arguments.multiple | |
import com.github.ajalt.clikt.parameters.options.default | |
import com.github.ajalt.clikt.parameters.options.flag | |
import com.github.ajalt.clikt.parameters.options.option | |
import com.github.ajalt.clikt.parameters.options.validate | |
import com.github.ajalt.clikt.parameters.types.choice | |
import com.github.ajalt.clikt.parameters.types.int | |
import com.github.ajalt.clikt.parameters.types.path | |
import java.io.IOException | |
import java.nio.file.Path | |
import kotlin.math.roundToInt | |
import kotlin.system.exitProcess | |
val helpMessage = """This script creates a webp animation from an image sequence. You need to have img2webp | |
available on your path for this to work. | |
See https://developers.google.com/speed/webp/docs/img2webp for more info.""" | |
inner class C2Webp : CliktCommand(help = helpMessage, printHelpOnEmptyArgs = true) { | |
private val sourceFiles: List<Path> by argument().path(mustExist = true).multiple() | |
private val outputFile: Path by argument().path() | |
private val loopCount: Int? by option("-loop").int().validate { it >= 0 } | |
private val fps: Int by option("-fps").int().default(10).validate { it > 0 } | |
private val quality: Int? by option("-q").int().validate { (0..100).contains(it) } | |
private val minSize: Boolean by option("-min_size").flag() | |
private val compression: String? by option("-c", "--compression").choice("lossy", "lossless", "mixed") | |
.default("lossless") | |
private val verbose: Boolean by option("-v", "--verbose").flag() | |
override fun run() { | |
if (!isInPath("img2webp")) { | |
System.err.println( | |
"\n❌ You need img2webp on the PATH. Install it following the instructions at " + | |
"https://developers.google.com/speed/webp/docs/precompiled" | |
) | |
exitProcess(1) | |
} | |
if (sourceFiles.isEmpty()) { | |
System.err.println("\n❌ You need to specify at least one source file(s)") | |
exitProcess(2) | |
} | |
if (verbose) { | |
println("\nℹ️ Input files:") | |
sourceFiles.forEach { | |
println("\t$it") | |
} | |
println("\nℹ️ Expected duration: ${sourceFiles.count() / fps * 1000} ms (approx.) @ ${fps}fps") | |
} | |
if (verbose) println("\nℹ️ Preparing img2webp command...") | |
val command = mutableListOf("img2webp") | |
if (loopCount != null) { | |
command += "-loop" | |
command += loopCount.toString() | |
} | |
if (minSize) command += "-min_size" | |
if (compression == "mixed") command += "-mixed" | |
if (verbose) command += "-v" | |
val frameDuration = (1 / fps.toFloat() * 1000).roundToInt() | |
val perFileArgs = mutableListOf("-d", frameDuration.toString()) | |
if (quality != null) { | |
perFileArgs += "-q" | |
perFileArgs += quality.toString() | |
} | |
if (compression != null && compression != "mixed") perFileArgs += "-$compression" | |
for (sourceFile in sourceFiles) { | |
command += sourceFile.toAbsolutePath().toString() | |
command += perFileArgs | |
} | |
command += "-o" | |
command += outputFile.toAbsolutePath().toString() | |
println("\nℹ️ Creating $outputFile...") | |
val exitCode = ProcessBuilder(command).inheritIO() | |
.start() | |
.waitFor() | |
if (exitCode == 0) { | |
println("\nℹ️ All done!\n") | |
} else { | |
System.err.println("\n❌ Something went wrong, check the output above") | |
if (!verbose) println("\tTip: use the -v flag to get verbose output and troubleshoot") | |
exitProcess(3) | |
} | |
} | |
} | |
fun isInPath(executableName: String): Boolean { | |
val builder = ProcessBuilder(if (isWindows()) "where" else "which", executableName) | |
return try { | |
val errCode = builder.start().waitFor() | |
errCode == 0 | |
} catch (ex: IOException) { | |
System.err.println("Error while looking for $executableName in PATH: ${ex.message}") | |
false | |
} catch (ex: InterruptedException) { | |
System.err.println("Error while looking for $executableName in PATH: ${ex.message}") | |
false | |
} | |
} | |
fun isWindows(): Boolean = System.getProperty("os.name").contains("windows", ignoreCase = true) | |
C2Webp().main(args) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment