diff --git a/src/main/kotlin/com/pischule/memevizor/bot/BotConfiguration.kt b/src/main/kotlin/com/pischule/memevizor/bot/BotConfiguration.kt index 4415e4f..06921f5 100644 --- a/src/main/kotlin/com/pischule/memevizor/bot/BotConfiguration.kt +++ b/src/main/kotlin/com/pischule/memevizor/bot/BotConfiguration.kt @@ -46,6 +46,7 @@ class BotConfiguration( photos { handleMedia() } video { handleMedia() } videoNote { handleMedia() } + document { handleMedia() } command("whoami") { handleMessage(message) { bot.sendMessage( diff --git a/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt b/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt index 8894b3e..50151d5 100644 --- a/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt +++ b/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt @@ -7,10 +7,20 @@ fun Message.getMedia(): MessageMedia? { return MessageMedia(fileId, MessageMedia.Type.PHOTO) } - (video?.fileId ?: videoNote?.fileId)?.let { fileId -> - return MessageMedia(fileId, MessageMedia.Type.VIDEO) + video?.let { + 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 } diff --git a/src/main/kotlin/com/pischule/memevizor/video/VideoTranscoderService.kt b/src/main/kotlin/com/pischule/memevizor/video/VideoTranscoderService.kt index d4e6086..6f400e2 100644 --- a/src/main/kotlin/com/pischule/memevizor/video/VideoTranscoderService.kt +++ b/src/main/kotlin/com/pischule/memevizor/video/VideoTranscoderService.kt @@ -1,6 +1,7 @@ package com.pischule.memevizor.video import io.github.oshai.kotlinlogging.KotlinLogging +import java.nio.file.Path import kotlin.io.path.createTempFile import kotlin.io.path.deleteIfExists import kotlin.io.path.readBytes @@ -14,62 +15,32 @@ private val logger = KotlinLogging.logger {} class VideoTranscoderService { fun transcode(inputVideo: ByteArray): ByteArray { - logger.info { "Started transcoding a file" } + logger.info { "Started transcoding a video" } val inputFile = createTempFile() val outputFile = createTempFile() - val command: List = - 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 { inputFile.writeBytes(inputVideo) - val process = ProcessBuilder(command).redirectErrorStream(true).start() - val exitCode: Int - val timeTaken = measureTime { exitCode = process.waitFor() } - val processOutput = process.inputStream.bufferedReader().use { it.readText() } - if (exitCode != 0) { - throw VideoTranscodingException(exitCode, processOutput) + val copyVideo = hasDesiredVideoEncoding(inputFile) + val copyAudio = hasDesiredAudioEncoding(inputFile) + + val processOutput: String + val timeTaken = measureTime { + processOutput = + launchCommand( + transcodeCommand( + inputFile = inputFile, + outputFile = outputFile, + copyVideo = copyVideo, + copyAudio = copyAudio, + ) + ) } logger.atInfo { - message = "Finished transcoding a file" + message = "Finished transcoding a video" payload = mapOf( "processOutput" to processOutput, @@ -82,4 +53,106 @@ class VideoTranscoderService { outputFile.deleteIfExists() } } + + private fun launchCommand(command: List): 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 = 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") + } }