From f206ee312dd8180c7d5886b174fb03e641c1fcb6 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:36:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8B=95=E7=94=BB=E3=81=AE=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=83=AD=E3=83=BC=E3=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + .../converter/MediaProcessServiceImpl.kt | 2 +- .../movie/MovieMediaProcessService.kt | 132 ++++++++++++++++++ src/main/resources/application.yml | 8 +- 4 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/movie/MovieMediaProcessService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 472a8f08..b0347c0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -181,6 +181,7 @@ dependencies { implementation("com.twelvemonkeys.imageio:imageio-webp:3.10.0") implementation("org.apache.tika:tika-core:2.9.1") implementation("net.coobird:thumbnailator:0.4.20") + implementation("org.bytedeco:javacv-platform:1.5.9") implementation("io.ktor:ktor-client-logging-jvm:$ktor_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessServiceImpl.kt index 0a1080c4..4fe892f6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessServiceImpl.kt @@ -13,7 +13,7 @@ class MediaProcessServiceImpl( private val thumbnailGenerateService: ThumbnailGenerateService ) : MediaProcessService { override fun isSupport(mimeType: MimeType): Boolean { - TODO("Not yet implemented") + return false } override suspend fun process( diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/movie/MovieMediaProcessService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/movie/MovieMediaProcessService.kt new file mode 100644 index 00000000..416fa698 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/movie/MovieMediaProcessService.kt @@ -0,0 +1,132 @@ +package dev.usbharu.hideout.core.service.media.converter.movie + +import dev.usbharu.hideout.core.service.media.FileType +import dev.usbharu.hideout.core.service.media.MimeType +import dev.usbharu.hideout.core.service.media.ProcessedMedia +import dev.usbharu.hideout.core.service.media.ProcessedMediaPath +import dev.usbharu.hideout.core.service.media.converter.MediaProcessService +import org.bytedeco.ffmpeg.global.avcodec +import org.bytedeco.javacv.FFmpegFrameFilter +import org.bytedeco.javacv.FFmpegFrameGrabber +import org.bytedeco.javacv.FFmpegFrameRecorder +import org.bytedeco.javacv.Java2DFrameConverter +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Service +import java.awt.image.BufferedImage +import java.nio.file.Files +import java.nio.file.Path +import javax.imageio.ImageIO +import kotlin.math.min + +@Service +@Qualifier("video") +class MovieMediaProcessService : MediaProcessService { + override fun isSupport(mimeType: MimeType): Boolean { + return mimeType.type == "video" + } + + override suspend fun process( + fileType: FileType, + contentType: String, + fileName: String, + file: ByteArray, + thumbnail: ByteArray? + ): ProcessedMedia { + TODO("Not yet implemented") + } + + override suspend fun process( + mimeType: MimeType, + fileName: String, + filePath: Path, + thumbnails: Path? + ): ProcessedMediaPath { + val tempFile = Files.createTempFile("hideout-movie-processor-", ".tmp") + val thumbnailFile = Files.createTempFile("hideout-movie-thumbnail-generate-", ".tmp") + logger.info("START Convert Movie Media {}", fileName) + FFmpegFrameGrabber(filePath.toFile()).use { grabber -> + grabber.start() + val width = grabber.imageWidth + val height = grabber.imageHeight + val frameRate = 60.0 + + logger.debug("Movie Media Width {}, Height {}", width, height) + + FFmpegFrameFilter( + "fps=fps=${frameRate.toInt()}", + "anull", + width, + height, + grabber.audioChannels + ).use { filter -> + + filter.sampleFormat = grabber.sampleFormat + filter.sampleRate = grabber.sampleRate + filter.pixelFormat = grabber.pixelFormat + filter.frameRate = grabber.frameRate + filter.start() + + val videoBitRate = min(1300000, (width * height * frameRate * 1 * 0.07).toInt()) + + logger.debug("Movie Media BitRate {}", videoBitRate) + + FFmpegFrameRecorder(tempFile.toFile(), width, height, grabber.audioChannels).use { + it.sampleRate = grabber.sampleRate + it.format = "mp4" + it.videoCodec = avcodec.AV_CODEC_ID_H264 + it.audioCodec = avcodec.AV_CODEC_ID_AAC + it.audioChannels = grabber.audioChannels + it.videoQuality = 1.0 + it.frameRate = frameRate + it.setVideoOption("preset", "ultrafast") + it.timestamp = 0 + it.gopSize = frameRate.toInt() + it.videoBitrate = videoBitRate + it.start() + + var bufferedImage: BufferedImage? = null + + val frameConverter = Java2DFrameConverter() + + while (true) { + val grab = grabber.grab() ?: break + + + if (bufferedImage == null) { + bufferedImage = frameConverter.convert(grab) + } + + if (grab.image != null || grab.samples != null) { + filter.push(grab) + } + while (true) { + val frame = filter.pull() ?: break + it.record(frame) + } + } + + + + if (bufferedImage != null) { + ImageIO.write(bufferedImage, "jpeg", thumbnailFile.toFile()) + } + } + } + + } + + logger.info("SUCCESS Convert Movie Media {}", fileName) + + return ProcessedMediaPath( + tempFile, + thumbnailFile, + MimeType("video", "mp4", FileType.Video), + MimeType("image", "jpeg", FileType.Image) + ) + } + + companion object { + private val logger = LoggerFactory.getLogger(MovieMediaProcessService::class.java) + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b792d06d..1aef91a7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -30,7 +30,10 @@ spring: database: hideout # username: hideoutuser # password: hideoutpass - + servlet: + multipart: + max-file-size: 40MB + max-request-size: 40MB h2: console: enabled: true @@ -42,5 +45,6 @@ server: basedir: tomcat accesslog: enabled: true - + max-http-form-post-size: 40MB + max-swallow-size: 40MB port: 8081