diff --git a/src/main/java/com/j9/bestmoments/BestMomentsApplication.java b/src/main/java/com/j9/bestmoments/BestMomentsApplication.java index 118762d..98d2732 100644 --- a/src/main/java/com/j9/bestmoments/BestMomentsApplication.java +++ b/src/main/java/com/j9/bestmoments/BestMomentsApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication +@EnableAsync public class BestMomentsApplication { public static void main(String[] args) { diff --git a/src/main/java/com/j9/bestmoments/service/FfmpegService.java b/src/main/java/com/j9/bestmoments/service/FfmpegService.java new file mode 100644 index 0000000..c9f8213 --- /dev/null +++ b/src/main/java/com/j9/bestmoments/service/FfmpegService.java @@ -0,0 +1,79 @@ +package com.j9.bestmoments.service; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import java.io.IOException; + +@Slf4j +@Service +public class FfmpegService { + + @Value("${ffmpeg.path}") + private String ffmpegPath; + + @Async + public void encodeVideo(String inputFilePath, String outputFilePath, String resolution) { + try { + ProcessBuilder processBuilder = new ProcessBuilder( + ffmpegPath, "-i", inputFilePath, "-s", resolution, "-codec:v", "libx264", outputFilePath + ); + + Process process = processBuilder.start(); + + // 로그 스트림 처리 + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); // 로그를 출력하거나 저장할 수 있습니다. + } + } + + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new RuntimeException("비디오 인코딩 실패"); + } + + log.info("인코딩 완료 : {}", outputFilePath); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + public String getVideoResolution(String inputFilePath) { + try { + ProcessBuilder processBuilder = new ProcessBuilder( + ffmpegPath, "-i", inputFilePath + ); + + Process process = processBuilder.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + String line; + String resolution = null; + + // FFmpeg의 출력에서 해상도를 파싱하여 추출 + while ((line = reader.readLine()) != null) { + if (line.contains("Video:")) { + String[] parts = line.split(","); + for (String part : parts) { + if (part.trim().matches("\\d{2,}x\\d{2,}")) { + resolution = part.trim(); // 해상도 부분을 찾음 + break; + } + } + } + } + + process.waitFor(); + return resolution; + + } catch (Exception e) { + e.printStackTrace(); + return "해상도 파싱 실패"; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/j9/bestmoments/service/VideoService.java b/src/main/java/com/j9/bestmoments/service/VideoService.java index 782a30b..eef68b0 100644 --- a/src/main/java/com/j9/bestmoments/service/VideoService.java +++ b/src/main/java/com/j9/bestmoments/service/VideoService.java @@ -9,8 +9,10 @@ import com.j9.bestmoments.service.storageService.LocalStorageService; import com.j9.bestmoments.util.FileNameGenerator; import jakarta.persistence.EntityNotFoundException; +import java.util.Arrays; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -24,6 +26,7 @@ public class VideoService { private final VideoRepository videoRepository; private final LocalStorageService storageService; + private final FfmpegService ffmpegService; @Transactional public Video upload(Member member, VideoCreateDto createDto) { @@ -34,18 +37,46 @@ public Video upload(Member member, VideoCreateDto createDto) { .description(createDto.description()) .build(); - String videoName = FileNameGenerator.generateVideoFileName(video, createDto.video()); - String videoUrl = storageService.uploadFile(createDto.video(), videoName); - video.setVideoUrl(videoUrl); + // 원본 영상 + String originVideoName = FileNameGenerator.generateVideoFileName(video, createDto.video()); + String originVideoUrl = storageService.uploadFile(createDto.video(), originVideoName); + video.setVideoUrl(originVideoUrl); + // 썸네일 이미지 String thumbnailName = FileNameGenerator.generateThumbnailImageFileName(video, createDto.thumbnail()); String thumbnailUrl = storageService.uploadFile(createDto.thumbnail(), thumbnailName); video.setThumbnailUrl(thumbnailUrl); + // 원본 사이즈 인코딩 + String resolution = ffmpegService.getVideoResolution(originVideoUrl); + String encodedVideoUrl = uploadEncodedVideo(originVideoUrl, resolution); + + // 1/2 사이즈 인코딩 + String halfResolution = Arrays.stream(resolution.split("x")) + .mapToInt(Integer::parseInt) + .map(value -> value / 2) + .mapToObj(String::valueOf) + .collect(Collectors.joining("x")); + String halfEncodedVideoUrl = uploadEncodedVideo(originVideoUrl, halfResolution); + + // 1/4 사이즈 인코딩 + String quarterResolution = Arrays.stream(resolution.split("x")) + .mapToInt(Integer::parseInt) + .map(value -> value / 4) + .mapToObj(String::valueOf) + .collect(Collectors.joining("x")); + String quarterEncodedVideoUrl = uploadEncodedVideo(originVideoUrl, quarterResolution); + videoRepository.save(video); return video; } + private String uploadEncodedVideo(String videoUrl, String resolution) { + String encodedVideoUrl = FileNameGenerator.generateEncodedVideoFileName(videoUrl, resolution); + ffmpegService.encodeVideo(videoUrl, encodedVideoUrl, resolution); + return encodedVideoUrl; + } + public Page