Skip to content

Commit 8c60ac0

Browse files
authored
refactor: 영상 처리 방식을 메모리에서 디스크로 전환 (#48)
1 parent 08fe948 commit 8c60ac0

3 files changed

Lines changed: 31 additions & 92 deletions

File tree

src/main/java/io/wisoft/prepair/prepair_api/service/answer/AnswerService.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
import io.wisoft.prepair.prepair_api.global.exception.BusinessException;
1313
import io.wisoft.prepair.prepair_api.global.exception.ErrorCode;
1414
import io.wisoft.prepair.prepair_api.repository.QuestionRepository;
15-
import java.io.IOException;
15+
1616
import java.nio.file.Files;
1717
import java.nio.file.Path;
1818
import java.util.UUID;
19+
1920
import lombok.RequiredArgsConstructor;
2021
import lombok.extern.slf4j.Slf4j;
2122
import org.springframework.stereotype.Service;
@@ -53,17 +54,22 @@ public void submitVideoAnswer(final UUID questionId, final UUID memberId, final
5354
String email = memberServiceClient.getMember(memberId).email();
5455
InterviewAnswer answer = answerPersistService.createVideoAnswer(questionId, memberId);
5556

56-
try {
57-
byte[] videoBytes = video.getBytes();
58-
Path videoPath = Files.createTempFile("video-", getExtension(video.getOriginalFilename()));
59-
Files.write(videoPath, videoBytes);
57+
log.info("[VIDEO] 영상 답변 처리 시작 - questionId: {}, memberId: {}", questionId, memberId);
58+
Path videoPath = createTempFile(video);
6059

61-
videoAnswerAnalyzer.uploadToS3(answer.getId(), videoBytes, video.getContentType(), video.getOriginalFilename(), email);
62-
videoAnswerAnalyzer.analyzeSTT(answer.getId(), questionId, memberId, videoPath, question.getQuestionTag());
63-
videoAnswerAnalyzer.analyzeVideo(answer.getId(), videoPath);
60+
videoAnswerAnalyzer.uploadToS3(answer.getId(), videoPath, video.getContentType(), email);
61+
videoAnswerAnalyzer.analyzeSTT(answer.getId(), questionId, memberId, videoPath, question.getQuestionTag());
62+
videoAnswerAnalyzer.analyzeVideo(answer.getId(), videoPath);
63+
log.info("[VIDEO] 비동기 작업 위임 완료 - answerId: {}", answer.getId());
64+
}
6465

65-
} catch (IOException e) {
66-
log.error("영상 임시파일 생성 실패", e);
66+
private Path createTempFile(MultipartFile video) {
67+
try {
68+
Path videoPath = Files.createTempFile("video-", getExtension(video.getOriginalFilename()));
69+
video.transferTo(videoPath);
70+
return videoPath;
71+
} catch (Exception e) {
72+
log.error("영상 임시 파일 생성 실패 - filename: {}", video.getOriginalFilename(), e);
6773
throw new BusinessException(ErrorCode.INTERNAL_ERROR);
6874
}
6975
}

src/main/java/io/wisoft/prepair/prepair_api/service/answer/VideoAnswerAnalyzer.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,21 @@ public class VideoAnswerAnalyzer {
3131
private final FeedbackGenerator feedbackGenerator;
3232

3333
@Async("videoTaskExecutor")
34-
public void uploadToS3(final UUID answerId, final byte[] videoBytes,
35-
final String contentType, final String originalFilename, final String email) {
34+
public void uploadToS3(final UUID answerId, final Path videoPath, final String contentType, final String email) {
35+
log.info("[VIDEO-S3] 업로드 시작 - answerId: {}", answerId);
3636
try {
37-
String mediaUrl = fileUploader.upload(videoBytes, contentType, originalFilename, email);
37+
String mediaUrl = fileUploader.upload(videoPath, contentType, email);
3838
answerPersistService.updateMediaUrl(answerId, mediaUrl);
39-
log.info("[S3] 업로드 완료 - answerId: {}", answerId);
39+
log.info("[VIDEO-S3] 업로드 완료 - answerId: {}", answerId);
4040
} catch (Exception e) {
41-
log.error("S3 업로드 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e);
41+
log.error("[VIDEO-S3] 업로드 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e);
4242
}
4343
}
4444

4545
@Async("videoTaskExecutor")
4646
public void analyzeSTT(final UUID answerId, final UUID questionId, final UUID memberId,
4747
final Path videoPath, final String questionTags) {
48+
log.info("[VIDEO-STT] 분석 시작 - answerId: {}", answerId);
4849
try {
4950
String answer = speechToTextService.convertToTextFromPath(videoPath, questionTags);
5051
answerPersistService.updateAnswer(answerId, answer);
@@ -56,22 +57,23 @@ public void analyzeSTT(final UUID answerId, final UUID questionId, final UUID me
5657
FeedbackDetail detail = new FeedbackDetail(result.good(), result.improvement(), result.recommendation());
5758

5859
answerPersistService.saveFeedback(answerId, result, detail, FeedbackType.STT);
59-
log.info("[STT] 완료 - answerId: {}", answerId);
60+
log.info("[VIDEO-STT] 분석 완료 - answerId: {}", answerId);
6061
} catch (Exception e) {
61-
log.error("STT 분석 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e);
62+
log.error("[VIDEO-STT] 분석 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e);
6263
}
6364
}
6465

6566
@Async("videoTaskExecutor")
6667
public void analyzeVideo(final UUID answerId, final Path videoPath) {
68+
log.info("[VIDEO-ANALYSIS] 분석 시작 - answerId: {}", answerId);
6769
try {
6870
FeedbackResult result = videoAnalysisService.analyze(videoPath);
6971
FeedbackDetail detail = new FeedbackDetail(result.good(), result.improvement(), result.recommendation());
7072

7173
answerPersistService.saveFeedback(answerId, result, detail, FeedbackType.VIDEO);
72-
log.info("[VIDEO] 완료 - answerId: {}", answerId);
74+
log.info("[VIDEO-ANALYSIS] 분석 완료 - answerId: {}", answerId);
7375
} catch (Exception e) {
74-
log.error("비디오 분석 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e);
76+
log.error("[VIDEO-ANALYSIS] 분석 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e);
7577
}
7678
}
7779
}

src/main/java/io/wisoft/prepair/prepair_api/storage/FileUploader.java

Lines changed: 4 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,43 @@
11
package io.wisoft.prepair.prepair_api.storage;
22

3-
import io.wisoft.prepair.prepair_api.global.exception.BusinessException;
4-
import io.wisoft.prepair.prepair_api.global.exception.ErrorCode;
5-
6-
import java.io.IOException;
73
import java.nio.file.Path;
8-
import java.time.Duration;
94
import java.time.LocalDate;
105
import java.util.UUID;
116

127
import lombok.RequiredArgsConstructor;
138
import lombok.extern.slf4j.Slf4j;
149
import org.springframework.beans.factory.annotation.Value;
1510
import org.springframework.stereotype.Component;
16-
import org.springframework.web.multipart.MultipartFile;
1711
import software.amazon.awssdk.core.sync.RequestBody;
1812
import software.amazon.awssdk.services.s3.S3Client;
19-
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
20-
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
2113
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
22-
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
2314

2415
@Slf4j
2516
@Component
2617
@RequiredArgsConstructor
2718
public class FileUploader {
2819

2920
private final S3Client s3Client;
30-
private final S3Presigner s3Presigner;
3121

3222
@Value("${cloud.aws.s3.bucket}")
3323
private String bucket;
3424

3525
@Value("${cloud.aws.s3.endpoint}")
3626
private String endpoint;
3727

38-
@Value("${cloud.aws.s3.presigned-url-expiration}")
39-
private long presignedUrlExpiration;
40-
41-
public String upload(MultipartFile file, String email) {
42-
try {
43-
return upload(file.getBytes(), file.getContentType(), file.getOriginalFilename(), email);
44-
} catch (IOException e) {
45-
log.error("영상 S3 업로드 실패 - bucket: {}, error: {}", bucket, e.getMessage(), e);
46-
throw new BusinessException(ErrorCode.FILE_UPLOAD_FAILED);
47-
}
48-
}
49-
50-
public String upload(byte[] bytes, String contentType, String originalFilename, String email) {
51-
String extension = getExtension(originalFilename);
28+
public String upload(Path videoPath, String contentType, String email) {
29+
String extension = getExtension(videoPath.getFileName().toString());
5230
String key = "interview-video/" + email + "/" + LocalDate.now() + "/" + UUID.randomUUID() + extension;
5331

5432
s3Client.putObject(PutObjectRequest.builder()
5533
.bucket(bucket)
5634
.key(key)
5735
.contentType(contentType)
5836
.build(),
59-
RequestBody.fromBytes(bytes)
60-
);
61-
62-
String url = endpoint + "/" + bucket + "/" + key;
63-
log.info("영상 S3 업로드 완료 - key: {}", key);
64-
return url;
65-
}
66-
67-
public Path download(String mediaUrl) {
68-
try {
69-
String key = extractKey(mediaUrl);
70-
Path tempFile = Path.of(System.getProperty("java.io.tmpdir"), "video-" + UUID.randomUUID() + ".tmp");
71-
72-
s3Client.getObject(GetObjectRequest.builder()
73-
.bucket(bucket)
74-
.key(key)
75-
.build(), tempFile);
76-
77-
log.info("영상 S3 다운로드 완료 - key: {}", key);
78-
return tempFile;
79-
} catch (Exception e) {
80-
log.error("영상 S3 다운로드 실패 - mediaUrl: {}, error: {}", mediaUrl, e.getMessage(), e);
81-
throw new BusinessException(ErrorCode.FILE_DOWNLOAD_FAILED);
82-
}
83-
}
84-
85-
public void delete(String mediaUrl) {
86-
String key = extractKey(mediaUrl);
87-
s3Client.deleteObject(DeleteObjectRequest.builder()
88-
.bucket(bucket)
89-
.key(key)
90-
.build()
37+
RequestBody.fromFile(videoPath)
9138
);
92-
log.info("영상 S3 삭제 완료 - key: {}", key);
93-
}
94-
95-
public String generatePresignedUrl(String mediaUrl) {
96-
String key = extractKey(mediaUrl);
97-
String presignedUrl = s3Presigner.presignGetObject(
98-
p -> p.signatureDuration(Duration.ofSeconds(presignedUrlExpiration))
99-
.getObjectRequest(
100-
g -> g.bucket(bucket).key(key).build()).build()
101-
).url().toString();
102-
103-
log.info("Presigned URL 발급 - key: {}", key);
104-
return presignedUrl;
105-
}
10639

107-
private String extractKey(String mediaUrl) {
108-
String prefix = endpoint + "/" + bucket + "/";
109-
return mediaUrl.substring(prefix.length());
40+
return endpoint + "/" + bucket + "/" + key;
11041
}
11142

11243
private String getExtension(String filename) {

0 commit comments

Comments
 (0)