From a26a5a7f6ba3a898b6c3839cfc80f4de7030e9e8 Mon Sep 17 00:00:00 2001 From: Hyunju Lee Date: Tue, 30 Jul 2024 17:18:05 +0900 Subject: [PATCH 1/7] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainingdiary/service/WorkoutSessionServiceTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/test/java/com/project/trainingdiary/service/WorkoutSessionServiceTest.java b/src/test/java/com/project/trainingdiary/service/WorkoutSessionServiceTest.java index 66d1977..57ebea6 100644 --- a/src/test/java/com/project/trainingdiary/service/WorkoutSessionServiceTest.java +++ b/src/test/java/com/project/trainingdiary/service/WorkoutSessionServiceTest.java @@ -771,8 +771,6 @@ void testUploadWorkoutImageSuccess() throws IOException { ); List capturedFiles = fileCaptor.getAllValues(); - List capturedKeys = keyCaptor.getAllValues(); - List capturedExtensions = extensionCaptor.getAllValues(); List capturedWidths = widthCaptor.getAllValues(); assertEquals("test.jpg", capturedFiles.get(0).getOriginalFilename()); @@ -904,8 +902,7 @@ void testUploadWorkoutImageFailInvalidFileType() throws IOException { @Test @DisplayName("동영상 업로드 성공") - public void testUploadWorkoutVideoSuccess() - throws IOException, InterruptedException { + public void testUploadWorkoutVideoSuccess() throws IOException, InterruptedException { Authentication authentication = new TestingAuthenticationToken("trainer@gmail.com", null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_TRAINER"))); SecurityContextHolder.getContext().setAuthentication(authentication); @@ -923,10 +920,8 @@ public void testUploadWorkoutVideoSuccess() .video(video).build(); String originalUrl = "https://test-bucket.s3.amazonaws.com/original"; - String thumbnailUrl = "https://test-bucket.s3.amazonaws.com/thumb"; when(s3VideoProvider.uploadVideo(eq(video), anyString())).thenReturn(originalUrl); - when(s3VideoProvider.uploadThumbnail(eq(originalUrl), anyString())).thenReturn(thumbnailUrl); WorkoutVideoResponseDto response = workoutSessionService.uploadWorkoutVideo(videoRequestDto); @@ -937,7 +932,6 @@ public void testUploadWorkoutVideoSuccess() assertEquals("VIDEO", savedMedia.getMediaType().name()); verify(s3VideoProvider, times(1)).uploadVideo(eq(video), anyString()); - verify(s3VideoProvider, times(1)).uploadThumbnail(eq(originalUrl), anyString()); ArgumentCaptor sessionCaptor = ArgumentCaptor .forClass(WorkoutSessionEntity.class); From 1c9476631effbc3b028c5cee516f69358cee933f Mon Sep 17 00:00:00 2001 From: Hyunju Lee Date: Tue, 30 Jul 2024 17:18:47 +0900 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/trainingdiary/service/WorkoutSessionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/project/trainingdiary/service/WorkoutSessionService.java b/src/main/java/com/project/trainingdiary/service/WorkoutSessionService.java index 6c79e18..0057fbe 100644 --- a/src/main/java/com/project/trainingdiary/service/WorkoutSessionService.java +++ b/src/main/java/com/project/trainingdiary/service/WorkoutSessionService.java @@ -271,7 +271,7 @@ public WorkoutVideoResponseDto uploadWorkoutVideo(WorkoutVideoRequestDto dto) String uuid = UUID.randomUUID().toString(); String originalUrl = s3VideoProvider.uploadVideo(video, uuid); - String thumbnailUrl = s3VideoProvider.uploadThumbnail(originalUrl, uuid); + String thumbnailUrl = s3VideoProvider.uploadThumbnail(video, uuid); WorkoutMediaEntity workoutMedia = WorkoutMediaEntity.builder() .originalUrl(originalUrl).thumbnailUrl(thumbnailUrl).mediaType(VIDEO).build(); From cadffd96d746a89e02efcd7b693ca8b2693e4f9a Mon Sep 17 00:00:00 2001 From: Hyunju Lee Date: Tue, 30 Jul 2024 17:19:19 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20temp=20=EB=B2=84=ED=82=B7=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-sample.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/application-sample.yml b/src/main/resources/application-sample.yml index ae12005..7184ee1 100644 --- a/src/main/resources/application-sample.yml +++ b/src/main/resources/application-sample.yml @@ -34,7 +34,6 @@ spring: static: s3: bucket: - temp-bucket: stack: auto: false From 1c347acfd5179cb257df65437b23e2ddf35ed3c4 Mon Sep 17 00:00:00 2001 From: Hyunju Lee Date: Tue, 30 Jul 2024 17:20:23 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20url=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/project/trainingdiary/config/SwaggerConfig.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java b/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java index 72dcfdc..752665b 100644 --- a/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java +++ b/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java @@ -11,6 +11,10 @@ description = "트레이너의 일정을 예약하고, 운동을 기록합니다." ), servers = { + @Server( + url = "http://ec2-3-36-95-58.ap-northeast-2.compute.amazonaws.com:8080", + description = "Video Test Server" + ), @Server( url = "https://api.training-diary.co.kr", description = "Stage Server" From 1ee5c813351c1ee4c1cb5353831c2e426dbec6de Mon Sep 17 00:00:00 2001 From: Hyunju Lee Date: Tue, 30 Jul 2024 17:22:22 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../provider/S3VideoProvider.java | 51 +++++-------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/project/trainingdiary/provider/S3VideoProvider.java b/src/main/java/com/project/trainingdiary/provider/S3VideoProvider.java index 0ee3b9d..10ad671 100644 --- a/src/main/java/com/project/trainingdiary/provider/S3VideoProvider.java +++ b/src/main/java/com/project/trainingdiary/provider/S3VideoProvider.java @@ -9,7 +9,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -22,15 +21,12 @@ public class S3VideoProvider { @Value("${spring.cloud.aws.s3.bucket}") private String bucket; - @Value("${spring.cloud.aws.s3.temp-bucket}") - private String tempBucket; - private final S3Operations s3Operations; public String uploadVideo(MultipartFile video, String uuid) throws IOException, InterruptedException { String extension = MediaUtil.getExtension(MediaUtil.checkFileNameExist(video)); - String tempKey = "temp_" + uuid + "." + extension; + String originalKey = "original_" + uuid + "." + (extension.equalsIgnoreCase("mov") ? "mp4" : extension); @@ -39,29 +35,15 @@ public String uploadVideo(MultipartFile video, String uuid) contentType = "video/mp4"; } - // 임시 버킷에 영상 업로드 - S3Resource tempS3Resource; - try (InputStream inputStream = video.getInputStream()) { - tempS3Resource = s3Operations.upload(tempBucket, tempKey, inputStream, - ObjectMetadata.builder().contentType(video.getContentType()).build()); - } - - String tempVideoUrl = tempS3Resource.getURL().toExternalForm(); - String encodedVideoUrl = encodeAndUploadVideo( - tempVideoUrl, originalKey, contentType, extension); - - // 임시 파일 삭제 - s3Operations.deleteObject(tempBucket, tempKey); - - return encodedVideoUrl; + return encodeAndUploadVideo(video, originalKey, contentType); } - public String uploadThumbnail(String encodedVideoUrl, String uuid) + public String uploadThumbnail(MultipartFile video, String uuid) throws IOException, InterruptedException { String thumbnailKey = "thumb_" + uuid + ".png"; String tmpPath = "/tmp/thumb_" + uuid + ".png"; - String thumbPath = VideoUtil.generateThumbnail(encodedVideoUrl, tmpPath); + String thumbPath = VideoUtil.generateThumbnail(video, tmpPath); S3Resource thumbS3Resource; File tempFile = new File(thumbPath); @@ -79,30 +61,21 @@ public String uploadThumbnail(String encodedVideoUrl, String uuid) } private String encodeAndUploadVideo( - String tempVideoUrl, + MultipartFile video, String originalKey, - String contentType, - String extension + String contentType ) throws IOException, InterruptedException { - // 임시 파일 경로 설정 + String tmpPath = "/tmp/" + originalKey; File tempFile = null; S3Resource videoS3Resource; try { - if ("mov".equalsIgnoreCase(extension)) { - String encodedVideoPath = VideoUtil.encodeVideo(tempVideoUrl, tmpPath); - tempFile = new File(encodedVideoPath); - try (InputStream inputStream = new FileInputStream(tempFile)) { - videoS3Resource = s3Operations.upload(bucket, originalKey, inputStream, - ObjectMetadata.builder().contentType(contentType).build()); - } - } else { - // MOV가 아닌 경우 원본을 그대로 사용 - try (InputStream inputStream = new URL(tempVideoUrl).openStream()) { - videoS3Resource = s3Operations.upload(bucket, originalKey, inputStream, - ObjectMetadata.builder().contentType(contentType).build()); - } + String encodedVideoPath = VideoUtil.encodeVideo(video, tmpPath); + tempFile = new File(encodedVideoPath); + try (InputStream inputStream = new FileInputStream(tempFile)) { + videoS3Resource = s3Operations.upload(bucket, originalKey, inputStream, + ObjectMetadata.builder().contentType(contentType).build()); } } finally { if (tempFile != null && tempFile.exists()) { From 494a1c5e1811cfdbb0fb20097c4f8b3703ea1dea Mon Sep 17 00:00:00 2001 From: Hyunju Lee Date: Tue, 30 Jul 2024 17:23:05 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20MultipartFile=20=EC=9E=90=EC=B2=B4?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=B8=EC=BD=94=EB=94=A9=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/trainingdiary/util/VideoUtil.java | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/project/trainingdiary/util/VideoUtil.java b/src/main/java/com/project/trainingdiary/util/VideoUtil.java index 908f810..89bc9d1 100644 --- a/src/main/java/com/project/trainingdiary/util/VideoUtil.java +++ b/src/main/java/com/project/trainingdiary/util/VideoUtil.java @@ -1,8 +1,13 @@ package com.project.trainingdiary.util; import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; +import org.springframework.web.multipart.MultipartFile; public class VideoUtil { @@ -11,14 +16,27 @@ public class VideoUtil { private static final String VIDEO_THUMBNAIL_WIDTH = "360:-1"; private static final String VIDEO_THUMBNAIL_HEIGHT = "-1:360"; - public static boolean isVerticalVideo(String inputUrl) throws IOException, InterruptedException { + public static boolean isVerticalVideo(MultipartFile file) + throws IOException, InterruptedException { + File tempFile = File.createTempFile("video", ".tmp"); + + try (InputStream inputStream = file.getInputStream()) { + try (OutputStream outputStream = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + } + ProcessBuilder processBuilder = new ProcessBuilder( "ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of", "default=noprint_wrappers=1:nokey=1", - inputUrl + tempFile.getAbsolutePath() ); Process process = processBuilder.start(); @@ -27,6 +45,8 @@ public static boolean isVerticalVideo(String inputUrl) throws IOException, Inter String heightLine = reader.readLine(); process.waitFor(); + tempFile.delete(); + if (widthLine != null && heightLine != null) { int width = Integer.parseInt(widthLine); int height = Integer.parseInt(heightLine); @@ -36,15 +56,27 @@ public static boolean isVerticalVideo(String inputUrl) throws IOException, Inter return false; } - public static String encodeVideo(String inputUrl, String outputUrl) + public static String encodeVideo(MultipartFile file, String outputUrl) throws IOException, InterruptedException { - boolean isVertical = isVerticalVideo(inputUrl); + boolean isVertical = isVerticalVideo(file); + ProcessBuilder processBuilder; + File tempFile = File.createTempFile("video", ".tmp"); + + try (InputStream inputStream = file.getInputStream()) { + try (OutputStream outputStream = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + } if (isVertical) { processBuilder = new ProcessBuilder( "ffmpeg", - "-i", inputUrl, + "-i", tempFile.getAbsolutePath(), "-vf", "scale=" + VIDEO_QUALITY_HEIGHT, "-preset", "medium", outputUrl @@ -52,7 +84,7 @@ public static String encodeVideo(String inputUrl, String outputUrl) } else { processBuilder = new ProcessBuilder( "ffmpeg", - "-i", inputUrl, + "-i", tempFile.getAbsolutePath(), "-vf", "scale=" + VIDEO_QUALITY_WIDTH, "-preset", "medium", outputUrl @@ -62,18 +94,33 @@ public static String encodeVideo(String inputUrl, String outputUrl) Process process = processBuilder.start(); process.waitFor(); + tempFile.delete(); + return outputUrl; + } - public static String generateThumbnail(String inputUrl, String outputUrl) + public static String generateThumbnail(MultipartFile file, String outputUrl) throws IOException, InterruptedException { - boolean isVertical = isVerticalVideo(inputUrl); + boolean isVertical = isVerticalVideo(file); + ProcessBuilder processBuilder; + File tempFile = File.createTempFile("video", ".tmp"); + + try (InputStream inputStream = file.getInputStream()) { + try (OutputStream outputStream = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + } if (isVertical) { processBuilder = new ProcessBuilder( "ffmpeg", - "-i", inputUrl, + "-i", tempFile.getAbsolutePath(), "-ss", "00:00:01.000", "-vframes", "1", "-vf", "scale=" + VIDEO_THUMBNAIL_WIDTH, @@ -83,7 +130,7 @@ public static String generateThumbnail(String inputUrl, String outputUrl) } else { processBuilder = new ProcessBuilder( "ffmpeg", - "-i", inputUrl, + "-i", tempFile.getAbsolutePath(), "-ss", "00:00:01.000", "-vframes", "1", "-vf", "scale=" + VIDEO_THUMBNAIL_HEIGHT, @@ -95,6 +142,8 @@ public static String generateThumbnail(String inputUrl, String outputUrl) Process process = processBuilder.start(); process.waitFor(); + tempFile.delete(); + return outputUrl; } From 6e750f78ec75bc1b5684ab0838076a06b70e114d Mon Sep 17 00:00:00 2001 From: Hyunju Lee Date: Tue, 30 Jul 2024 17:31:22 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/project/trainingdiary/config/SwaggerConfig.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java b/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java index 752665b..72dcfdc 100644 --- a/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java +++ b/src/main/java/com/project/trainingdiary/config/SwaggerConfig.java @@ -11,10 +11,6 @@ description = "트레이너의 일정을 예약하고, 운동을 기록합니다." ), servers = { - @Server( - url = "http://ec2-3-36-95-58.ap-northeast-2.compute.amazonaws.com:8080", - description = "Video Test Server" - ), @Server( url = "https://api.training-diary.co.kr", description = "Stage Server"