Skip to content

Commit

Permalink
Merge pull request #46 from Team-J9/feature/encode
Browse files Browse the repository at this point in the history
인코딩된 영상 링크들을 제공하도록 수정
  • Loading branch information
SJ70 authored Oct 12, 2024
2 parents a9ea37f + ac968fa commit 094a626
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.j9.bestmoments.converter;

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.Arrays;
import java.util.List;

@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {

private static final String SEPARATOR = ",";

@Override
public String convertToDatabaseColumn(List<String> attribute) {
return String.join(SEPARATOR, attribute);
}

@Override
public List<String> convertToEntityAttribute(String dbData) {
return Arrays.asList(dbData.split(SEPARATOR));
}

}
13 changes: 12 additions & 1 deletion src/main/java/com/j9/bestmoments/domain/Video.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.j9.bestmoments.domain;

import com.j9.bestmoments.converter.StringListConverter;
import jakarta.persistence.Convert;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
Expand All @@ -19,7 +21,6 @@
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

Expand All @@ -32,8 +33,13 @@ public class Video {

@Id
private UUID id;

private String videoUrl;
private String thumbnailUrl;
@Lob
@Convert(converter = StringListConverter.class)
private List<String> encodedVideoUrls;

private String title;
@Lob
private String description;
Expand All @@ -60,6 +66,7 @@ public Video(Member uploader, String title, String description, VideoStatus vide
this.description = description;
this.videoStatus = videoStatus;
this.isDeleted = false;
this.encodedVideoUrls = new ArrayList<>();
}

public void softDelete() {
Expand Down Expand Up @@ -90,6 +97,10 @@ public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}

public void addEncodedVideoUrl(String videoUrl) {
this.encodedVideoUrls.add(videoUrl);
}

public void setThumbnailUrl(String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import com.j9.bestmoments.domain.Video;
import com.j9.bestmoments.domain.VideoStatus;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

public record VideoFindDto(
UUID id,
String videoUrl,
List<String> videoUrls,
String thumbnailUrl,
String title,
String description,
Expand All @@ -20,7 +21,7 @@ public record VideoFindDto(
public static VideoFindDto of (Video video) {
return new VideoFindDto(
video.getId(),
video.getVideoUrl(),
video.getEncodedVideoUrls(),
video.getThumbnailUrl(),
video.getTitle(),
video.getDescription(),
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/j9/bestmoments/service/FfmpegService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.j9.bestmoments.service;

import jakarta.persistence.criteria.CriteriaBuilder.In;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -18,6 +19,17 @@ public class FfmpegService {
@Async
public void encodeVideo(String inputFilePath, String outputFilePath, String resolution) {
try {
// 해상도가 홀수인 경우 조정
int width = Integer.parseInt(resolution.split("x")[0]);
int height = Integer.parseInt(resolution.split("x")[0]);
if (width % 2 == 1) {
width++;
}
if (height % 2 == 1) {
height++;
}
resolution = width + "x" + height;

ProcessBuilder processBuilder = new ProcessBuilder(
ffmpegPath, "-i", inputFilePath, "-s", resolution, "-codec:v", "libx264", outputFilePath
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.j9.bestmoments.util;
package com.j9.bestmoments.service;

import com.j9.bestmoments.domain.Member;
import com.j9.bestmoments.domain.Video;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.web.multipart.MultipartFile;

public final class FileNameGenerator {
public final class FileNameProvider {

public static String generateProfileImageFileName(Member member, MultipartFile file) {
String memberId = member.getId().toString();
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/com/j9/bestmoments/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
import com.j9.bestmoments.domain.Member;
import com.j9.bestmoments.repository.MemberRepository;
import com.j9.bestmoments.service.storageService.StorageService;
import com.j9.bestmoments.util.FileNameGenerator;
import jakarta.persistence.EntityNotFoundException;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -68,7 +66,7 @@ public Member update(Member member, MemberUpdateDto memberUpdateDto) {
member.setDescription(memberUpdateDto.description());
}
if (memberUpdateDto.file() != null) {
String fileName = FileNameGenerator.generateProfileImageFileName(member, memberUpdateDto.file());
String fileName = FileNameProvider.generateProfileImageFileName(member, memberUpdateDto.file());
String profileImageUrl = googleCloudStorageService.uploadFile(memberUpdateDto.file(), fileName);
member.setProfileImageUrl(profileImageUrl);
}
Expand Down
49 changes: 26 additions & 23 deletions src/main/java/com/j9/bestmoments/service/VideoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import com.j9.bestmoments.dto.request.VideoUpdateDto;
import com.j9.bestmoments.repository.VideoRepository;
import com.j9.bestmoments.service.storageService.LocalStorageService;
import com.j9.bestmoments.util.FileNameGenerator;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.criteria.CriteriaBuilder.In;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
Expand All @@ -28,6 +28,8 @@ public class VideoService {
private final LocalStorageService storageService;
private final FfmpegService ffmpegService;

private final static int[] STANDARD_RESOLUTION_HEIGHTS = {144, 240, 360, 480, 720, 1080, 1440, 2160};

@Transactional
public Video upload(Member member, VideoCreateDto createDto) {
Video video = Video.builder()
Expand All @@ -38,41 +40,42 @@ public Video upload(Member member, VideoCreateDto createDto) {
.build();

// 원본 영상
String originVideoName = FileNameGenerator.generateVideoFileName(video, createDto.video());
String originVideoName = FileNameProvider.generateVideoFileName(video, createDto.video());
String originVideoUrl = storageService.uploadFile(createDto.video(), originVideoName);
video.setVideoUrl(originVideoUrl);

// 썸네일 이미지
String thumbnailName = FileNameGenerator.generateThumbnailImageFileName(video, createDto.thumbnail());
String thumbnailName = FileNameProvider.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);
String originalResolution = ffmpegService.getVideoResolution(originVideoUrl);
String originalSizeEncodedVideoUrl = uploadEncodedVideo(originVideoUrl, originalResolution);

int originalWidth = Integer.parseInt(originalResolution.split("x")[0]);
int originalHeight = Integer.parseInt(originalResolution.split("x")[1]);

for (int height : STANDARD_RESOLUTION_HEIGHTS) {
// 원본 화질보다 작은 화질로만 인코딩
if (height >= originalHeight) {
break;
}
int width = originalWidth * height / originalHeight;
String resolution = width + "x" + height;
String encodedVideoUrl = uploadEncodedVideo(originVideoUrl, resolution);
video.addEncodedVideoUrl(encodedVideoUrl);
}

// 가장 큰 원본 화질은 가장 마지막에 추가
video.addEncodedVideoUrl(originalSizeEncodedVideoUrl);

videoRepository.save(video);
return video;
}

private String uploadEncodedVideo(String videoUrl, String resolution) {
String encodedVideoUrl = FileNameGenerator.generateEncodedVideoFileName(videoUrl, resolution);
String encodedVideoUrl = FileNameProvider.generateEncodedVideoFileName(videoUrl, resolution);
ffmpegService.encodeVideo(videoUrl, encodedVideoUrl, resolution);
return encodedVideoUrl;
}
Expand Down Expand Up @@ -109,7 +112,7 @@ public Video findPublicById(UUID id) {
@Transactional
public Video update(Video video, VideoUpdateDto updateDto) {
if (updateDto.thumbnail() != null) {
String thumbnailName = FileNameGenerator.generateThumbnailImageFileName(video, updateDto.thumbnail());
String thumbnailName = FileNameProvider.generateThumbnailImageFileName(video, updateDto.thumbnail());
String thumbnailUrl = storageService.uploadFile(updateDto.thumbnail(), thumbnailName);
video.setThumbnailUrl(thumbnailUrl);
}
Expand Down

0 comments on commit 094a626

Please sign in to comment.