Optimize transcoding

Skip transcoding if audio/video already has desired encoding
This commit is contained in:
2025-12-07 19:00:47 +03:00
parent 1266b0f440
commit 061743492e
3 changed files with 132 additions and 48 deletions

View File

@@ -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(

View File

@@ -7,8 +7,18 @@ 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

View File

@@ -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")
}
} }