Skip to content

Commit

Permalink
Merge pull request #178 from TrainingDiary/fix/ffmpeg-video
Browse files Browse the repository at this point in the history
동영상 업로드 수정
  • Loading branch information
guswnee00 authored Jul 30, 2024
2 parents d8f3302 + 6e750f7 commit 5d874c0
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
69 changes: 59 additions & 10 deletions src/main/java/com/project/trainingdiary/util/VideoUtil.java
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -36,23 +56,35 @@ 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
);
} else {
processBuilder = new ProcessBuilder(
"ffmpeg",
"-i", inputUrl,
"-i", tempFile.getAbsolutePath(),
"-vf", "scale=" + VIDEO_QUALITY_WIDTH,
"-preset", "medium",
outputUrl
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -95,6 +142,8 @@ public static String generateThumbnail(String inputUrl, String outputUrl)
Process process = processBuilder.start();
process.waitFor();

tempFile.delete();

return outputUrl;
}

Expand Down
1 change: 0 additions & 1 deletion src/main/resources/application-sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ spring:
static:
s3:
bucket:
temp-bucket:
stack:
auto: false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -771,8 +771,6 @@ void testUploadWorkoutImageSuccess() throws IOException {
);

List<MultipartFile> capturedFiles = fileCaptor.getAllValues();
List<String> capturedKeys = keyCaptor.getAllValues();
List<String> capturedExtensions = extensionCaptor.getAllValues();
List<Integer> capturedWidths = widthCaptor.getAllValues();

assertEquals("test.jpg", capturedFiles.get(0).getOriginalFilename());
Expand Down Expand Up @@ -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("[email protected]", null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_TRAINER")));
SecurityContextHolder.getContext().setAuthentication(authentication);
Expand All @@ -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);

Expand All @@ -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<WorkoutSessionEntity> sessionCaptor = ArgumentCaptor
.forClass(WorkoutSessionEntity.class);
Expand Down

0 comments on commit 5d874c0

Please sign in to comment.