mirror of
https://github.com/pischule/memevizor.git
synced 2025-12-19 06:56:42 +00:00
Optimize transcoding
Skip transcoding if audio/video already has desired encoding
This commit is contained in:
@@ -46,6 +46,7 @@ class BotConfiguration(
|
|||||||
photos { handleMedia() }
|
photos { handleMedia() }
|
||||||
video { handleMedia() }
|
video { handleMedia() }
|
||||||
videoNote { handleMedia() }
|
videoNote { handleMedia() }
|
||||||
|
document { handleMedia() }
|
||||||
command("whoami") {
|
command("whoami") {
|
||||||
handleMessage(message) {
|
handleMessage(message) {
|
||||||
bot.sendMessage(
|
bot.sendMessage(
|
||||||
|
|||||||
@@ -7,10 +7,20 @@ fun Message.getMedia(): MessageMedia? {
|
|||||||
return MessageMedia(fileId, MessageMedia.Type.PHOTO)
|
return MessageMedia(fileId, MessageMedia.Type.PHOTO)
|
||||||
}
|
}
|
||||||
|
|
||||||
(video?.fileId ?: videoNote?.fileId)?.let { fileId ->
|
video?.let {
|
||||||
return MessageMedia(fileId, MessageMedia.Type.VIDEO)
|
return MessageMedia(it.fileId, MessageMedia.Type.VIDEO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoNote?.let {
|
||||||
|
return MessageMedia(it.fileId, MessageMedia.Type.VIDEO)
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
?.takeIf { it.mimeType?.startsWith("video/") == true }
|
||||||
|
?.let {
|
||||||
|
return MessageMedia(it.fileId, MessageMedia.Type.VIDEO)
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.pischule.memevizor.video
|
package com.pischule.memevizor.video
|
||||||
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.createTempFile
|
import kotlin.io.path.createTempFile
|
||||||
import kotlin.io.path.deleteIfExists
|
import kotlin.io.path.deleteIfExists
|
||||||
import kotlin.io.path.readBytes
|
import kotlin.io.path.readBytes
|
||||||
@@ -14,62 +15,32 @@ private val logger = KotlinLogging.logger {}
|
|||||||
class VideoTranscoderService {
|
class VideoTranscoderService {
|
||||||
|
|
||||||
fun transcode(inputVideo: ByteArray): ByteArray {
|
fun transcode(inputVideo: ByteArray): ByteArray {
|
||||||
logger.info { "Started transcoding a file" }
|
logger.info { "Started transcoding a video" }
|
||||||
|
|
||||||
val inputFile = createTempFile()
|
val inputFile = createTempFile()
|
||||||
val outputFile = createTempFile()
|
val outputFile = createTempFile()
|
||||||
|
|
||||||
val command: List<String> =
|
|
||||||
listOf(
|
|
||||||
"ffmpeg",
|
|
||||||
"-nostdin",
|
|
||||||
"-nostats",
|
|
||||||
"-hide_banner",
|
|
||||||
// input
|
|
||||||
"-i",
|
|
||||||
"$inputFile",
|
|
||||||
// video
|
|
||||||
"-map",
|
|
||||||
"0:v",
|
|
||||||
"-c:v",
|
|
||||||
"libsvtav1",
|
|
||||||
"-preset",
|
|
||||||
"6",
|
|
||||||
"-crf",
|
|
||||||
"35",
|
|
||||||
"-svtav1-params",
|
|
||||||
"film-grain=10",
|
|
||||||
// audio
|
|
||||||
"-map",
|
|
||||||
"0:a?",
|
|
||||||
"-c:a",
|
|
||||||
"libopus",
|
|
||||||
"-b:a",
|
|
||||||
"96k",
|
|
||||||
"-vbr",
|
|
||||||
"on",
|
|
||||||
"-compression_level",
|
|
||||||
"10",
|
|
||||||
// output
|
|
||||||
"-f",
|
|
||||||
"webm",
|
|
||||||
"-y",
|
|
||||||
"$outputFile",
|
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
inputFile.writeBytes(inputVideo)
|
inputFile.writeBytes(inputVideo)
|
||||||
|
|
||||||
val process = ProcessBuilder(command).redirectErrorStream(true).start()
|
val copyVideo = hasDesiredVideoEncoding(inputFile)
|
||||||
val exitCode: Int
|
val copyAudio = hasDesiredAudioEncoding(inputFile)
|
||||||
val timeTaken = measureTime { exitCode = process.waitFor() }
|
|
||||||
val processOutput = process.inputStream.bufferedReader().use { it.readText() }
|
val processOutput: String
|
||||||
if (exitCode != 0) {
|
val timeTaken = measureTime {
|
||||||
throw VideoTranscodingException(exitCode, processOutput)
|
processOutput =
|
||||||
|
launchCommand(
|
||||||
|
transcodeCommand(
|
||||||
|
inputFile = inputFile,
|
||||||
|
outputFile = outputFile,
|
||||||
|
copyVideo = copyVideo,
|
||||||
|
copyAudio = copyAudio,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.atInfo {
|
logger.atInfo {
|
||||||
message = "Finished transcoding a file"
|
message = "Finished transcoding a video"
|
||||||
payload =
|
payload =
|
||||||
mapOf(
|
mapOf(
|
||||||
"processOutput" to processOutput,
|
"processOutput" to processOutput,
|
||||||
@@ -82,4 +53,106 @@ class VideoTranscoderService {
|
|||||||
outputFile.deleteIfExists()
|
outputFile.deleteIfExists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchCommand(command: List<String>): String {
|
||||||
|
val process = ProcessBuilder(command).redirectErrorStream(true).start()
|
||||||
|
val exitCode = process.waitFor()
|
||||||
|
val processOutput = process.inputStream.bufferedReader().use { it.readText() }
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw VideoTranscodingException(exitCode, processOutput)
|
||||||
|
}
|
||||||
|
return processOutput.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasDesiredVideoEncoding(inputFile: Path): Boolean {
|
||||||
|
val videoCodec = launchCommand(getVideoCodecCommand(inputFile))
|
||||||
|
return videoCodec == "av1"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasDesiredAudioEncoding(inputFile: Path): Boolean {
|
||||||
|
val audioCodec = launchCommand(getAudioCodecCommand(inputFile))
|
||||||
|
return audioCodec == "opus" || audioCodec == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVideoCodecCommand(inputFile: Path) =
|
||||||
|
listOf(
|
||||||
|
"ffprobe",
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-select_streams",
|
||||||
|
"v:0",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=codec_name",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
"$inputFile",
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getAudioCodecCommand(inputFile: Path) =
|
||||||
|
listOf(
|
||||||
|
"ffprobe",
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-select_streams",
|
||||||
|
"a:0",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=codec_name",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
"$inputFile",
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun transcodeCommand(
|
||||||
|
inputFile: Path,
|
||||||
|
outputFile: Path,
|
||||||
|
copyVideo: Boolean,
|
||||||
|
copyAudio: Boolean,
|
||||||
|
): List<String> = buildList {
|
||||||
|
add("ffmpeg")
|
||||||
|
add("-nostdin")
|
||||||
|
add("-nostats")
|
||||||
|
add("-hide_banner")
|
||||||
|
|
||||||
|
// input
|
||||||
|
add("-i")
|
||||||
|
add("$inputFile")
|
||||||
|
|
||||||
|
// video
|
||||||
|
add("-map")
|
||||||
|
add("0:v")
|
||||||
|
add("-c:v")
|
||||||
|
if (copyVideo) {
|
||||||
|
add("copy")
|
||||||
|
} else {
|
||||||
|
add("libsvtav1")
|
||||||
|
add("-preset")
|
||||||
|
add("6")
|
||||||
|
add("-crf")
|
||||||
|
add("35")
|
||||||
|
add("-svtav1-params")
|
||||||
|
add("film-grain=10")
|
||||||
|
}
|
||||||
|
|
||||||
|
// audio
|
||||||
|
add("-map")
|
||||||
|
add("0:a?")
|
||||||
|
add("-c:a")
|
||||||
|
if (copyAudio) {
|
||||||
|
add("copy")
|
||||||
|
} else {
|
||||||
|
add("libopus")
|
||||||
|
add("-b:a")
|
||||||
|
add("96k")
|
||||||
|
add("-vbr")
|
||||||
|
add("on")
|
||||||
|
add("-compression_level")
|
||||||
|
add("10")
|
||||||
|
}
|
||||||
|
|
||||||
|
// output
|
||||||
|
add("-f")
|
||||||
|
add("webm")
|
||||||
|
add("-y")
|
||||||
|
add("$outputFile")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user